Skip to content

Commit

Permalink
Add support for expanding .env quoted values.
Browse files Browse the repository at this point in the history
Switch from the dotenvy crate to dotenvs to achieve this. A patched
version is used to ensure we maintain support for hard failure when
invalid `.env` files are read.

Fixes a-scie#166
Fixes a-scie#167
  • Loading branch information
jsirois committed Dec 21, 2023
1 parent ba7e038 commit 436a350
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 20 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release Notes

## 0.14.0

Change `.env` parsing libraries to gain support for double quoted values with variable
substitution; e.g.: the `.env` line `PYTHONPATH="/Users/A. Space:$PYTHONPATH"` now has the
`$PYTHONPATH` portion of the value substituted.

## 0.13.3

Ensure liblzma is statically linked.
Expand Down
28 changes: 23 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ members = [

[package]
name = "scie-jump"
version = "0.13.3"
version = "0.14.0"
description = "The self contained interpreted executable launcher."
authors = [
"John Sirois <[email protected]>",
Expand Down
35 changes: 31 additions & 4 deletions docs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,36 @@ In the "lift" manifest, there are 3 required fields:

### Optional fields

A scie "lift" can opt in to loading `.env` files via the "load_dotenv" boolean field. The [dotenv](
https://crates.io/crates/dotenv) crate handles this loading. A lift's files and commands can also
have additional configuration metadata described.
A scie "lift" can opt in to loading `.env` files via the "load_dotenv" boolean field. The [dotenvs](
https://crates.io/crates/dotenvs) crate handles this loading and the following behavior is
guaranteed:

Parsing rules:

- `BASIC=basic` and `export BASIC=basic` both export an environment variable named `BASIC` with
value `basic`.
- Empty lines are skipped.
- Lines beginning with `#` are treated as comments.
- A `#` marks the beginning of a comment (unless when the value is wrapped in quotes).
- Empty values become empty strings.
- Inner quotes are maintained.
- Whitespace is removed from both ends of unquoted values.
- Single and double quoted values maintain whitespace from both ends.
- Double quoted values expand new lines, i.e.: `MULTILINE="new\nline"` becomes:
```
MULTILINE: "new
line"
```

Expanding rules:

- `$KEY` will expand any env with the name `KEY` (unless the value is wrapped in single quotes).
- `${KEY}` will expand any env with the name `KEY` (unless the value is wrapped in single quotes).
- `\$KEY` will escape the `$KEY` rather than expand.
- `${KEY:-default}` will first attempt to expand any env with the name `KEY`. If not one, then it
will return `default`.

A lift's files and commands can also have additional configuration metadata described.

A scie "lift" can also establish a custom `nce` cache directory via the "base" string field. Any
placeholders present in the custom value will be expanded save for the `{scie.lift}` placeholder
Expand Down Expand Up @@ -400,4 +427,4 @@ analyze and measure your use case for applicability when considering making a sc
creates an extra file called the `scie-tote` that is a zip that stores all the files above it inside
as STORED (uncompressed) entries. You need not be aware of this, the scie still functions like you'd
expect. Its only when using a tool like `zipinfo` to inspect your scie executable that you'll notice
a zip file entry for each of the files you specified.
a zip file entry for each of the files you specified.
12 changes: 12 additions & 0 deletions examples/load/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,20 @@ source .env
grep "${GET_CONFIG}" "${GET_LOG_CONFIG}"

# Motivated by: https://github.com/pantsbuild/scie-pants/issues/307
# And ammended by: https://github.com/a-scie/jump/issues/166
# shellcheck disable=SC2016 # We with this text to be included verbatim in the .env file.
echo 'PYTHONPATH="/foo/bar:$PYTHONPATH"' >> .env
./cowsay "Should succeed!"

# See motivating case here: https://github.com/arniu/dotenvs-rs/issues/4
cat << EOF >> .env
A=foo bar
B="notenough
C='toomany''
D=valid
export NOT_SET
E=valid
EOF
if ./cowsay "Should fail!"; then
die "The expected .env file loading failure did not happen."
else
Expand Down
7 changes: 5 additions & 2 deletions jump/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jump"
version = "0.13.3"
version = "0.14.0"
description = "The bulk of the scie-jump binary logic."
authors = [
"John Sirois <[email protected]>",
Expand All @@ -13,7 +13,6 @@ bstr = { workspace = true }
byteorder = "1.4"
bzip2 = "0.4"
dirs = "4.0"
dotenvy = "0.15"
fd-lock = "3.0"
flate2 = "1.0" # For gz support.
indexmap = { version = "1.9", features = ["serde"] }
Expand All @@ -35,6 +34,10 @@ zip = { workspace = true }
zstd = "0.12"
walkdir = "2.3"

[dependencies.dotenvs]
git = "https://github.com/jsirois/dotenvs-rs"
rev = "b2276ef3fd039ed8565b4c1cbedb7a5aeeca734e"

[dev-dependencies]
ctor = "0.2"
env_logger = { workspace = true }
Expand Down
23 changes: 15 additions & 8 deletions jump/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,20 +180,27 @@ pub fn prepare_boot() -> Result<BootAction, String> {

if lift.load_dotenv {
let _timer = timer!(Level::Debug; "jump::load_dotenv");
match dotenvy::dotenv() {
Ok(dotenv_file) => debug!("Loaded env file from {path}", path = dotenv_file.display()),
Err(err) if err.not_found() => {
match dotenv::from_filename(".env") {
Ok(env) => {
let mut iter = env.iter();
while let Some((key, value)) = iter.try_next().map_err(|err| {
format!(
"This scie requested .env files be loaded but there was an error doing so: \
{err}"
)
})? {
if std::env::var(key).is_err() {
std::env::set_var(key, value);
}
}
}
Err(_) => {
debug!(
"No .env files found for invocation of {current_exe} from cwd of {cwd:?}",
current_exe = current_exe.exe.display(),
cwd = env::current_dir()
)
}
Err(err) => {
return Err(format!(
"This scie requested .env files be loaded but there was an error doing so: {err}"
))
}
}
}
let payload = &data[jump.size..data.len() - lift.size];
Expand Down

0 comments on commit 436a350

Please sign in to comment.