From 2e9fffde405c0d67f7f3b36e620b2fab4358a530 Mon Sep 17 00:00:00 2001 From: becker Date: Wed, 1 May 2024 23:49:24 -0700 Subject: [PATCH] 1748-dotenv-files Dearest Reviewer, I was looking at the parser and the format for a list of things [] was already support for shells. I reused that concept to make an ordered list of strings. This now works ``` set dotenv-files := [".env1", ".env2"] ``` As does the command line ``` just --dotenv-files .env1 --dotenv-files .env2 echo ``` The new setting is applied last. It does not change how the other two settings currently work. I do have a problem I cant get this to work ``` ENV := "prod" set dotenv-files := [".env.{{ENV}}", ".env"] ``` it the `{{ENV}}` never turns in to prod. I am using parse_string_literal. the issue was updated about fallback justfiles. I have not used them and I would take any advice on what to change for that as well. Any advice would be appreciated Thanks Becker --- src/config.rs | 18 +++++++++++++++++- src/keyword.rs | 1 + src/lib.rs | 24 +++++++++++++----------- src/load_dotenv.rs | 24 ++++++++++++++++++++++-- src/node.rs | 5 +++++ src/ordered_list.rs | 23 +++++++++++++++++++++++ src/parser.rs | 25 ++++++++++++++++++++++++- src/setting.rs | 4 ++++ src/settings.rs | 8 ++++++++ tests/json.rs | 18 ++++++++++++++++++ 10 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 src/ordered_list.rs diff --git a/src/config.rs b/src/config.rs index 21a6cf8021..40383c88d4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,6 +23,7 @@ pub(crate) struct Config { pub(crate) color: Color, pub(crate) command_color: Option, pub(crate) dotenv_filename: Option, + pub(crate) dotenv_files: Option>, pub(crate) dotenv_path: Option, pub(crate) dry_run: bool, pub(crate) dump_format: DumpFormat, @@ -98,6 +99,7 @@ mod arg { pub(crate) const COMMAND_COLOR: &str = "COMMAND-COLOR"; pub(crate) const DOTENV_FILENAME: &str = "DOTENV-FILENAME"; pub(crate) const DOTENV_PATH: &str = "DOTENV-PATH"; + pub(crate) const DOTENV_FILES: &str = "DOTENV-FILES"; pub(crate) const DRY_RUN: &str = "DRY-RUN"; pub(crate) const DUMP_FORMAT: &str = "DUMP-FORMAT"; pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT"; @@ -398,7 +400,18 @@ impl Config { .long("dotenv-filename") .takes_value(true) .help("Search for environment file named instead of `.env`") - .conflicts_with(arg::DOTENV_PATH), + .conflicts_with(arg::DOTENV_PATH) + .conflicts_with(arg::DOTENV_FILES), + ) + .arg( + Arg::with_name(arg::DOTENV_FILES) + .long("dotenv-files") + .takes_value(true) + .multiple(true) + .number_of_values(1) + .help("Search for environment list of files.") + .conflicts_with(arg::DOTENV_PATH) + .conflicts_with(arg::DOTENV_FILENAME), ) .arg( Arg::with_name(arg::DOTENV_PATH) @@ -653,6 +666,9 @@ impl Config { command_color, dotenv_filename: matches.value_of(arg::DOTENV_FILENAME).map(str::to_owned), dotenv_path: matches.value_of(arg::DOTENV_PATH).map(PathBuf::from), + dotenv_files: matches + .values_of(arg::DOTENV_FILES) + .map(|value| value.map(PathBuf::from).collect()), dry_run: matches.is_present(arg::DRY_RUN), dump_format: Self::dump_format_from_matches(matches)?, highlight: !matches.is_present(arg::NO_HIGHLIGHT), diff --git a/src/keyword.rs b/src/keyword.rs index 830207e55b..be0089ade0 100644 --- a/src/keyword.rs +++ b/src/keyword.rs @@ -8,6 +8,7 @@ pub(crate) enum Keyword { DotenvFilename, DotenvLoad, DotenvPath, + DotenvFiles, Else, Export, Fallback, diff --git a/src/lib.rs b/src/lib.rs index 445e819019..478be4c665 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,17 +25,18 @@ pub(crate) use { fragment::Fragment, function::Function, function_context::FunctionContext, interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List, - load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal, - output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind, - parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position, - positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe, - recipe_context::RecipeContext, recipe_resolver::RecipeResolver, scope::Scope, search::Search, - search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting, - settings::Settings, shebang::Shebang, shell::Shell, show_whitespace::ShowWhitespace, - source::Source, string_kind::StringKind, string_literal::StringLiteral, subcommand::Subcommand, - suggestion::Suggestion, table::Table, thunk::Thunk, token::Token, token_kind::TokenKind, - unresolved_dependency::UnresolvedDependency, unresolved_recipe::UnresolvedRecipe, - use_color::UseColor, variables::Variables, verbosity::Verbosity, warning::Warning, + load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, + ordered_list::OrderedList, ordinal::Ordinal, output::output, output_error::OutputError, + parameter::Parameter, parameter_kind::ParameterKind, parser::Parser, platform::Platform, + platform_interface::PlatformInterface, position::Position, positional::Positional, ran::Ran, + range_ext::RangeExt, recipe::Recipe, recipe_context::RecipeContext, + recipe_resolver::RecipeResolver, scope::Scope, search::Search, search_config::SearchConfig, + search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang, + shell::Shell, show_whitespace::ShowWhitespace, source::Source, string_kind::StringKind, + string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table, + thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency, + unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables, + verbosity::Verbosity, warning::Warning, }, std::{ cmp, @@ -151,6 +152,7 @@ mod load_dotenv; mod loader; mod name; mod namepath; +mod ordered_list; mod ordinal; mod output; mod output_error; diff --git a/src/load_dotenv.rs b/src/load_dotenv.rs index fb129d2b4e..a572dd8df6 100644 --- a/src/load_dotenv.rs +++ b/src/load_dotenv.rs @@ -17,7 +17,18 @@ pub(crate) fn load_dotenv( .as_ref() .or(settings.dotenv_path.as_ref()); - if !settings.dotenv_load.unwrap_or_default() && dotenv_filename.is_none() && dotenv_path.is_none() + let dotenv_files = config.dotenv_files.as_ref().or_else(|| { + if settings.dotenv_files.is_empty() { + None + } else { + Some(settings.dotenv_files.as_ref()) + } + }); + + if !settings.dotenv_load.unwrap_or_default() + && dotenv_filename.is_none() + && dotenv_path.is_none() + && dotenv_files.is_none() { return Ok(BTreeMap::new()); } @@ -35,7 +46,16 @@ pub(crate) fn load_dotenv( } } - Ok(BTreeMap::new()) + let mut return_tree = BTreeMap::new(); + if let Some(dotenv_files) = dotenv_files { + for path in dotenv_files.iter() { + match load_from_file(&path) { + Ok(mut env_tree) => return_tree.append(&mut env_tree), + Err(error) => return Err(error), + } + } + } + Ok(return_tree) } fn load_from_file(path: &Path) -> RunResult<'static, BTreeMap> { diff --git a/src/node.rs b/src/node.rs index 1d98b99173..44c0ad270b 100644 --- a/src/node.rs +++ b/src/node.rs @@ -284,6 +284,11 @@ impl<'src> Node<'src> for Set<'src> { Setting::DotenvFilename(value) | Setting::DotenvPath(value) | Setting::Tempdir(value) => { set.push_mut(Tree::string(value)); } + Setting::DotenvFiles(ordered_list) => { + for item in ordered_list.list.iter() { + set.push_mut(Tree::string(&item.cooked)); + } + } } set diff --git a/src/ordered_list.rs b/src/ordered_list.rs new file mode 100644 index 0000000000..058f80f5e3 --- /dev/null +++ b/src/ordered_list.rs @@ -0,0 +1,23 @@ +use super::*; + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct OrderedList<'src> { + pub(crate) list: Vec>, +} + +impl<'src> Display for OrderedList<'src> { + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + write!(f, "[")?; + write!( + f, + "{}", + self + .list + .iter() + .map(|v| v.cooked.as_ref()) + .collect::>() + .join(", ") + )?; + write!(f, "]") + } +} diff --git a/src/parser.rs b/src/parser.rs index ba006ac5bd..eaedcc671f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,4 @@ -use {super::*, TokenKind::*}; +use {self::ordered_list::OrderedList, super::*, TokenKind::*}; /// Just language parser /// @@ -866,6 +866,7 @@ impl<'run, 'src> Parser<'run, 'src> { let set_value = match keyword { Keyword::DotenvFilename => Some(Setting::DotenvFilename(self.parse_string_literal()?.cooked)), Keyword::DotenvPath => Some(Setting::DotenvPath(self.parse_string_literal()?.cooked)), + Keyword::DotenvFiles => Some(Setting::DotenvFiles(self.parse_ordered_list()?)), Keyword::Shell => Some(Setting::Shell(self.parse_shell()?)), Keyword::Tempdir => Some(Setting::Tempdir(self.parse_string_literal()?.cooked)), Keyword::WindowsShell => Some(Setting::WindowsShell(self.parse_shell()?)), @@ -881,6 +882,28 @@ impl<'run, 'src> Parser<'run, 'src> { })) } + /// Parse a shell setting value + fn parse_ordered_list(&mut self) -> CompileResult<'src, OrderedList<'src>> { + let mut list = Vec::new(); + self.expect(BracketL)?; + + list.push(self.parse_string_literal()?); + + if self.accepted(Comma)? { + while !self.next_is(BracketR) { + list.push(self.parse_string_literal()?); + + if !self.accepted(Comma)? { + break; + } + } + } + + self.expect(BracketR)?; + + Ok(OrderedList { list }) + } + /// Parse a shell setting value fn parse_shell(&mut self) -> CompileResult<'src, Shell<'src>> { self.expect(BracketL)?; diff --git a/src/setting.rs b/src/setting.rs index 0256ff94c9..898877f93e 100644 --- a/src/setting.rs +++ b/src/setting.rs @@ -6,6 +6,7 @@ pub(crate) enum Setting<'src> { DotenvFilename(String), DotenvLoad(bool), DotenvPath(String), + DotenvFiles(OrderedList<'src>), Export(bool), Fallback(bool), IgnoreComments(bool), @@ -29,6 +30,9 @@ impl<'src> Display for Setting<'src> { | Setting::Quiet(value) | Setting::WindowsPowerShell(value) => write!(f, "{value}"), Setting::Shell(shell) | Setting::WindowsShell(shell) => write!(f, "{shell}"), + Setting::DotenvFiles(value) => { + write!(f, "{value}") + } Setting::DotenvFilename(value) | Setting::DotenvPath(value) | Setting::Tempdir(value) => { write!(f, "{value:?}") } diff --git a/src/settings.rs b/src/settings.rs index f72cb5465e..eee3fe6274 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -9,6 +9,7 @@ pub(crate) const WINDOWS_POWERSHELL_ARGS: &[&str] = &["-NoLogo", "-Command"]; pub(crate) struct Settings<'src> { pub(crate) allow_duplicate_recipes: bool, pub(crate) dotenv_filename: Option, + pub(crate) dotenv_files: Vec, pub(crate) dotenv_load: Option, pub(crate) dotenv_path: Option, pub(crate) export: bool, @@ -67,6 +68,13 @@ impl<'src> Settings<'src> { Setting::Tempdir(tempdir) => { settings.tempdir = Some(tempdir); } + Setting::DotenvFiles(ordered_list) => { + settings.dotenv_files = ordered_list + .list + .iter() + .map(|path| PathBuf::from(&path.cooked)) + .collect(); + } } } diff --git a/tests/json.rs b/tests/json.rs index 4c71b430ff..0cbd399d0e 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -45,6 +45,7 @@ fn alias() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -81,6 +82,7 @@ fn assignment() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -132,6 +134,7 @@ fn body() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -195,6 +198,7 @@ fn dependencies() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -295,6 +299,7 @@ fn dependency_argument() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -358,6 +363,7 @@ fn duplicate_recipes() { "settings": { "allow_duplicate_recipes": true, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -402,6 +408,7 @@ fn doc_comment() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -432,6 +439,7 @@ fn empty_justfile() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -583,6 +591,7 @@ fn parameters() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -667,6 +676,7 @@ fn priors() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -711,6 +721,7 @@ fn private() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -755,6 +766,7 @@ fn quiet() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -811,6 +823,7 @@ fn settings() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": "filename", + "dotenv_files": [], "dotenv_load": true, "dotenv_path": "path", "export": true, @@ -861,6 +874,7 @@ fn shebang() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -905,6 +919,7 @@ fn simple() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -952,6 +967,7 @@ fn attribute() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -1012,6 +1028,7 @@ fn module() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false, @@ -1031,6 +1048,7 @@ fn module() { "settings": { "allow_duplicate_recipes": false, "dotenv_filename": null, + "dotenv_files": [], "dotenv_load": null, "dotenv_path": null, "export": false,