diff --git a/Cargo.lock b/Cargo.lock index fe4ee0e..2a051a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,14 @@ name = "ansi_term" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "assert_matches" version = "1.1.0" @@ -21,11 +29,32 @@ name = "atty" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "backtrace" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "1.0.1" @@ -40,6 +69,16 @@ dependencies = [ "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cc" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "clap" version = "2.30.0" @@ -54,6 +93,25 @@ dependencies = [ "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "derive-error-chain" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dotenv" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "derive-error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "edit-distance" version = "2.0.1" @@ -64,6 +122,14 @@ name = "either" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "error-chain" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "executable-path" version = "1.0.0" @@ -90,7 +156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "itertools" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -100,16 +166,17 @@ dependencies = [ name = "just" version = "0.3.8" dependencies = [ - "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "assert_matches 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "brev 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "executable-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "target 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -132,7 +199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.36" +version = "0.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -140,16 +207,21 @@ name = "memchr" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rand" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -192,11 +264,34 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc-demangle" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "strsim" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "target" version = "1.0.0" @@ -216,7 +311,7 @@ name = "termion" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -243,6 +338,11 @@ name = "unicode-width" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unreachable" version = "1.0.0" @@ -298,35 +398,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" "checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum assert_matches 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9e772942dccdf11b368c31e044e4fca9189f80a773d2f0808379de65894cbf57" "checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" +"checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2" +"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" "checksum brev 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "d85c3b7957223c752ff78ffd20a1806b0c7262d9aef85ed470546f16b56a5bb2" +"checksum cc 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9be26b24e988625409b19736d130f0c7d224f01d06454b5f81d8d23d6c1a618f" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1c07b9257a00f3fc93b7f3c417fc15607ec7a56823bc2c37ec744e266387de5b" +"checksum derive-error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92183014af72c63aea490e66526c712bf1066ac50f66c9f34824f02483ec1d98" +"checksum dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a70de3c590ce18df70743cace1cf12565637a0b26fd8b04ef10c7d33fdc66cdc" "checksum edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3bd26878c3d921f89797a4e1a1711919f999a9f6946bb6f5a4ffda126d297b7e" "checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3" +"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" "checksum executable-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ebc5a6d89e3c90b84e8f33c8737933dda8f1c106b5415900b38b9d433841478" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b07332223953b5051bceb67e8c4700aa65291535568e1f12408c43c4a42c0394" +"checksum itertools 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "23d53b4c7394338044c3b9c8c5b2caaf7b40ae049ecd321578ebdc2e13738cd1" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" -"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" +"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "5be5347bde0c48cfd8c3fdc0766cdfe9d8a755ef84d620d6794c778c91de8b2b" "checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" "checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5" +"checksum rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11fb43a206a04116ffd7cfcf9bcb941f8eb6cc7ff667272246b0a1c74259a3cb" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum target 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10000465bb0cc031c87a44668991b284fd84c0e6bd945f62d4af04e9e52a222a" "checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" diff --git a/Cargo.toml b/Cargo.toml index 4bedba7..32964a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,12 @@ homepage = "https://github.com/casey/just" readme = "crates-io-readme.md" [dependencies] -ansi_term = "0.10" +ansi_term = "0.11" assert_matches = "1.1.0" atty = "0.2.1" brev = "0.1.6" clap = "2.0.0" +dotenv = "0.11.0" edit-distance = "2.0.0" itertools = "0.7" lazy_static = "1.0.0" diff --git a/README.adoc b/README.adoc index c177405..4d02b6d 100644 --- a/README.adoc +++ b/README.adoc @@ -289,6 +289,34 @@ This is an x86_64 machine - `env_var_or_default(key, default)` – Retrieves the environment variable with name `key`, returning `default` if it is not present. +==== Dotenv Integration + +`just` will load environment variables from a file named `.env`. This file can be located in the same directory as your justfile or in a parent directory. These variables are environment variables, not `just` variables, and so must be accessed using `$VARIABLE_NAME` in recipes and backticks. + +For example, if your `.env` file contains: + +``` +# a comment, will be ignored +DATABASE_ADDRESS=localhost:6379 +SERVER_PORT=1337 +``` + +And your justfile contains: + +```make +serve: + @echo "Starting server with database $DATABASE_ADDRESS on port $SERVER_PORT..." + ./server --database $DATABASE_ADDRESS --port $SERVER_PORT +``` + +`just serve` will output: + +```sh +$ just serve +Starting server with database localhost:6379 on port 1337... +./server --database $DATABASE_ADDRESS --port $SERVER_PORT +``` + === Command Evaluation Using Backticks Backticks can be used to store the result of commands: diff --git a/src/assignment_evaluator.rs b/src/assignment_evaluator.rs index 8ad1daa..51ec54e 100644 --- a/src/assignment_evaluator.rs +++ b/src/assignment_evaluator.rs @@ -4,32 +4,35 @@ use brev; pub struct AssignmentEvaluator<'a: 'b, 'b> { pub assignments: &'b Map<&'a str, Expression<'a>>, + pub dotenv: &'b Map, + pub dry_run: bool, pub evaluated: Map<&'a str, String>, pub exports: &'b Set<&'a str>, pub overrides: &'b Map<&'b str, &'b str>, pub quiet: bool, pub scope: &'b Map<&'a str, String>, pub shell: &'b str, - pub dry_run: bool, } impl<'a, 'b> AssignmentEvaluator<'a, 'b> { pub fn evaluate_assignments( assignments: &Map<&'a str, Expression<'a>>, + dotenv: &'b Map, overrides: &Map<&str, &str>, quiet: bool, shell: &'a str, dry_run: bool, ) -> RunResult<'a, Map<&'a str, String>> { let mut evaluator = AssignmentEvaluator { - assignments: assignments, - evaluated: empty(), - exports: &empty(), - overrides: overrides, - quiet: quiet, - scope: &empty(), - shell: shell, - dry_run: dry_run, + evaluated: empty(), + exports: &empty(), + scope: &empty(), + assignments, + dotenv, + dry_run, + overrides, + quiet, + shell, }; for name in assignments.keys() { @@ -110,7 +113,7 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { if self.dry_run { Ok(format!("`{}`", raw)) } else { - Ok(self.run_backtick(raw, token)?) + Ok(self.run_backtick(self.dotenv, raw, token)?) } } Expression::Concatination{ref lhs, ref rhs} => { @@ -125,12 +128,13 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { fn run_backtick( &self, + dotenv: &Map, raw: &str, token: &Token<'a>, ) -> RunResult<'a, String> { let mut cmd = Command::new(self.shell); - cmd.export_environment_variables(self.scope, self.exports)?; + cmd.export_environment_variables(self.scope, dotenv, self.exports)?; cmd.arg("-cu") .arg(raw); diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs index 637754b..07abbbe 100644 --- a/src/assignment_resolver.rs +++ b/src/assignment_resolver.rs @@ -17,11 +17,11 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { ) -> CompilationResult<'a, ()> { let mut resolver = AssignmentResolver { - assignments: assignments, - assignment_tokens: assignment_tokens, - stack: empty(), - seen: empty(), - evaluated: empty(), + stack: empty(), + seen: empty(), + evaluated: empty(), + assignments, + assignment_tokens, }; for name in assignments.keys() { diff --git a/src/color.rs b/src/color.rs index 053c5d6..3cd039e 100644 --- a/src/color.rs +++ b/src/color.rs @@ -35,7 +35,7 @@ impl Default for Color { impl Color { fn restyle(self, style: Style) -> Color { Color { - style: style, + style, ..self } } diff --git a/src/command_ext.rs b/src/command_ext.rs index 82086d5..ea02080 100644 --- a/src/command_ext.rs +++ b/src/command_ext.rs @@ -3,7 +3,8 @@ use common::*; pub trait CommandExt { fn export_environment_variables<'a>( &mut self, - scope: &Map<&'a str, String>, + scope: &Map<&'a str, String>, + dotenv: &Map, exports: &Set<&'a str> ) -> RunResult<'a, ()>; } @@ -11,9 +12,13 @@ pub trait CommandExt { impl CommandExt for Command { fn export_environment_variables<'a>( &mut self, - scope: &Map<&'a str, String>, + scope: &Map<&'a str, String>, + dotenv: &Map, exports: &Set<&'a str> ) -> RunResult<'a, ()> { + for (name, value) in dotenv { + self.env(name, value); + } for name in exports { if let Some(value) = scope.get(name) { self.env(name, value); diff --git a/src/cooked_string.rs b/src/cooked_string.rs index 8b6eccd..a886fa1 100644 --- a/src/cooked_string.rs +++ b/src/cooked_string.rs @@ -11,7 +11,7 @@ impl<'a> CookedString<'a> { let raw = &token.lexeme[1..token.lexeme.len()-1]; if let TokenKind::RawString = token.kind { - Ok(CookedString{raw: raw, cooked: raw.to_string()}) + Ok(CookedString{cooked: raw.to_string(), raw}) } else if let TokenKind::StringToken = token.kind { let mut cooked = String::new(); let mut escape = false; @@ -36,7 +36,7 @@ impl<'a> CookedString<'a> { } cooked.push(c); } - Ok(CookedString{raw: raw, cooked: cooked}) + Ok(CookedString{raw, cooked}) } else { Err(token.error(CompilationErrorKind::Internal { message: "cook_string() called on non-string token".to_string() diff --git a/src/justfile.rs b/src/justfile.rs index 379cc1d..5d81556 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -53,8 +53,11 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b { return Err(RuntimeError::UnknownOverrides{overrides: unknown_overrides}); } + let dotenv = load_dotenv()?; + let scope = AssignmentEvaluator::evaluate_assignments( &self.assignments, + &dotenv, &configuration.overrides, configuration.quiet, configuration.shell, @@ -107,12 +110,12 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b { } else { None }; - return Err(RuntimeError::UnknownRecipes{recipes: missing, suggestion: suggestion}); + return Err(RuntimeError::UnknownRecipes{recipes: missing, suggestion}); } let mut ran = empty(); for (recipe, arguments) in grouped { - self.run_recipe(recipe, arguments, &scope, &mut ran, configuration)? + self.run_recipe(recipe, arguments, &scope, &dotenv, configuration, &mut ran)? } Ok(()) @@ -123,15 +126,16 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b { recipe: &Recipe<'a>, arguments: &[&'a str], scope: &Map<&'c str, String>, - ran: &mut Set<&'a str>, + dotenv: &Map, configuration: &Configuration<'a>, + ran: &mut Set<&'a str>, ) -> RunResult<()> { for dependency_name in &recipe.dependencies { if !ran.contains(dependency_name) { - self.run_recipe(&self.recipes[dependency_name], &[], scope, ran, configuration)?; + self.run_recipe(&self.recipes[dependency_name], &[], scope, dotenv, configuration, ran)?; } } - recipe.run(arguments, scope, &self.exports, configuration)?; + recipe.run(arguments, scope, dotenv, &self.exports, configuration)?; ran.insert(recipe.name); Ok(()) } diff --git a/src/lexer.rs b/src/lexer.rs index 6db8dcb..83905ff 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -41,12 +41,12 @@ impl<'a> Lexer<'a> { pub fn lex(text: &'a str) -> CompilationResult>> { let lexer = Lexer{ tokens: vec![], - text: text, rest: text, index: 0, line: 0, column: 0, state: vec![State::Start], + text, }; lexer.inner() @@ -59,7 +59,7 @@ impl<'a> Lexer<'a> { line: self.line, column: self.column, width: None, - kind: kind, + kind, } } @@ -69,9 +69,9 @@ impl<'a> Lexer<'a> { line: self.line, column: self.column, text: self.text, - prefix: prefix, - lexeme: lexeme, - kind: kind, + prefix, + lexeme, + kind, } } diff --git a/src/load_dotenv.rs b/src/load_dotenv.rs new file mode 100644 index 0000000..e20c47d --- /dev/null +++ b/src/load_dotenv.rs @@ -0,0 +1,17 @@ +use common::*; + +use dotenv; + +pub fn load_dotenv() -> RunResult<'static, Map> { + match dotenv::dotenv_iter() { + Ok(iter) => { + let result: dotenv::Result> = iter.collect(); + result.map_err(|dotenv_error| RuntimeError::Dotenv{dotenv_error}) + } + Err(dotenv_error) => if dotenv_error.not_found() { + Ok(Map::new()) + } else { + Err(RuntimeError::Dotenv{dotenv_error}) + } + } +} diff --git a/src/main.rs b/src/main.rs index 28f3aa4..816e0fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate lazy_static; extern crate ansi_term; extern crate brev; extern crate clap; +extern crate dotenv; extern crate edit_distance; extern crate itertools; extern crate libc; @@ -22,6 +23,7 @@ mod command_ext; mod compilation_error; mod configuration; mod cooked_string; +mod load_dotenv; mod expression; mod fragment; mod functions; @@ -63,6 +65,8 @@ mod common { pub use expression::Expression; pub use fragment::Fragment; pub use justfile::Justfile; + pub use lexer::Lexer; + pub use load_dotenv::load_dotenv; pub use misc::{default, empty}; pub use parameter::Parameter; pub use parser::Parser; @@ -70,7 +74,6 @@ mod common { pub use recipe::Recipe; pub use recipe_resolver::RecipeResolver; pub use runtime_error::{RuntimeError, RunResult}; - pub use lexer::Lexer; pub use shebang::Shebang; pub use token::{Token, TokenKind}; } diff --git a/src/parser.rs b/src/parser.rs index 8c49034..e163c09 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -22,12 +22,12 @@ impl<'a> Parser<'a> { pub fn new(text: &'a str, tokens: Vec>) -> Parser<'a> { Parser { - text: text, tokens: itertools::put_back(tokens), recipes: empty(), assignments: empty(), assignment_tokens: empty(), exports: empty(), + text, } } @@ -151,10 +151,10 @@ impl<'a> Parser<'a> { parsed_variadic_parameter = variadic; parameters.push(Parameter { - default: default, name: parameter.lexeme, token: parameter, - variadic: variadic, + default, + variadic, }); } @@ -237,13 +237,13 @@ impl<'a> Parser<'a> { line_number: name.line, name: name.lexeme, doc: doc.map(|t| t.lexeme[1..].trim()), - dependencies: dependencies, - dependency_tokens: dependency_tokens, - parameters: parameters, private: &name.lexeme[0..1] == "_", - lines: lines, - shebang: shebang, - quiet: quiet, + dependencies, + dependency_tokens, + lines, + parameters, + quiet, + shebang, }); Ok(()) diff --git a/src/recipe.rs b/src/recipe.rs index ab8e9f3..9b04f35 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -12,8 +12,8 @@ fn error_from_signal( exit_status: ExitStatus ) -> RuntimeError { match Platform::signal_from_exit_status(exit_status) { - Some(signal) => RuntimeError::Signal{recipe: recipe, line_number: line_number, signal: signal}, - None => RuntimeError::Unknown{recipe: recipe, line_number: line_number}, + Some(signal) => RuntimeError::Signal{recipe, line_number, signal}, + None => RuntimeError::Unknown{recipe, line_number}, } } @@ -52,6 +52,7 @@ impl<'a> Recipe<'a> { &self, arguments: &[&'a str], scope: &Map<&'a str, String>, + dotenv: &Map, exports: &Set<&'a str>, configuration: &Configuration, ) -> RunResult<'a, ()> { @@ -84,14 +85,15 @@ impl<'a> Recipe<'a> { } let mut evaluator = AssignmentEvaluator { - evaluated: empty(), - scope: scope, - exports: exports, assignments: &empty(), + dry_run: configuration.dry_run, + evaluated: empty(), overrides: &empty(), quiet: configuration.quiet, shell: configuration.shell, - dry_run: configuration.dry_run, + dotenv, + exports, + scope, }; if self.shebang { @@ -153,13 +155,13 @@ impl<'a> Recipe<'a> { let mut command = Platform::make_shebang_command(&path, interpreter, argument) .map_err(|output_error| RuntimeError::Cygpath{recipe: self.name, output_error})?; - command.export_environment_variables(scope, exports)?; + command.export_environment_variables(scope, dotenv, exports)?; // run it! match command.status() { Ok(exit_status) => if let Some(code) = exit_status.code() { if code != 0 { - return Err(RuntimeError::Code{recipe: self.name, line_number: None, code: code}) + return Err(RuntimeError::Code{recipe: self.name, line_number: None, code}) } } else { return Err(error_from_signal(self.name, None, exit_status)) @@ -168,7 +170,7 @@ impl<'a> Recipe<'a> { recipe: self.name, command: interpreter.to_string(), argument: argument.map(String::from), - io_error: io_error + io_error, }) }; } else { @@ -228,20 +230,22 @@ impl<'a> Recipe<'a> { cmd.stdout(Stdio::null()); } - cmd.export_environment_variables(scope, exports)?; + cmd.export_environment_variables(scope, dotenv, exports)?; match cmd.status() { Ok(exit_status) => if let Some(code) = exit_status.code() { if code != 0 { return Err(RuntimeError::Code{ - recipe: self.name, line_number: Some(line_number), code: code + recipe: self.name, line_number: Some(line_number), code, }); } } else { return Err(error_from_signal(self.name, Some(line_number), exit_status)); }, Err(io_error) => return Err(RuntimeError::IoError{ - recipe: self.name, io_error: io_error}), + recipe: self.name, + io_error, + }), }; } } diff --git a/src/recipe_resolver.rs b/src/recipe_resolver.rs index d2ce0e3..415eef6 100644 --- a/src/recipe_resolver.rs +++ b/src/recipe_resolver.rs @@ -16,10 +16,10 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { text: &'a str, ) -> CompilationResult<'a, ()> { let mut resolver = RecipeResolver { - seen: empty(), - stack: empty(), - resolved: empty(), - recipes: recipes, + seen: empty(), + stack: empty(), + resolved: empty(), + recipes, }; for recipe in recipes.values() { @@ -44,14 +44,14 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { for (function, argc) in expression.functions() { if let Err(error) = ::functions::resolve_function(function, argc) { return Err(CompilationError { - text: text, index: error.index, line: error.line, column: error.column, width: error.width, kind: UnknownFunction { function: &text[error.index..error.index + error.width.unwrap()], - } + }, + text, }); } } @@ -62,14 +62,14 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { if undefined { let error = variable.error(UndefinedVariable{variable: name}); return Err(CompilationError { - text: text, index: error.index, line: error.line, column: error.column, width: error.width, kind: UndefinedVariable { variable: &text[error.index..error.index + error.width.unwrap()], - } + }, + text, }); } } diff --git a/src/run.rs b/src/run.rs index 35aa65a..34e838c 100644 --- a/src/run.rs +++ b/src/run.rs @@ -349,11 +349,11 @@ pub fn run() { dry_run: matches.is_present("DRY-RUN"), evaluate: matches.is_present("EVALUATE"), highlight: matches.is_present("HIGHLIGHT"), - overrides: overrides, quiet: matches.is_present("QUIET"), shell: matches.value_of("SHELL").unwrap(), - color: color, verbose: matches.is_present("VERBOSE"), + color, + overrides, }; if let Err(run_error) = justfile.run(&arguments, &configuration) { diff --git a/src/runtime_error.rs b/src/runtime_error.rs index db0c103..663341a 100644 --- a/src/runtime_error.rs +++ b/src/runtime_error.rs @@ -1,5 +1,7 @@ use common::*; +use dotenv; + use brev::OutputError; use misc::{And, Or, maybe_s, Tick, ticks, write_error_context}; @@ -25,6 +27,7 @@ pub enum RuntimeError<'a> { Backtick{token: Token<'a>, output_error: OutputError}, Code{recipe: &'a str, line_number: Option, code: i32}, Cygpath{recipe: &'a str, output_error: OutputError}, + Dotenv{dotenv_error: dotenv::Error}, FunctionCall{token: Token<'a>, message: String}, Internal{message: String}, IoError{recipe: &'a str, io_error: io::Error}, @@ -118,6 +121,9 @@ impl<'a> Display for RuntimeError<'a> { but output was not utf8: {}", recipe, utf8_error)?; } }, + Dotenv{ref dotenv_error} => { + write!(f, "Failed to load .env: {}\n", dotenv_error)?; + } FunctionCall{ref token, ref message} => { write!(f, "Call to function `{}` failed: {}\n", token.lexeme, message)?; error_token = Some(token); diff --git a/src/token.rs b/src/token.rs index 9ca0c79..20335f3 100644 --- a/src/token.rs +++ b/src/token.rs @@ -14,12 +14,12 @@ pub struct Token<'a> { impl<'a> Token<'a> { pub fn error(&self, kind: CompilationErrorKind<'a>) -> CompilationError<'a> { CompilationError { - text: self.text, + column: self.column + self.prefix.len(), index: self.index + self.prefix.len(), line: self.line, - column: self.column + self.prefix.len(), + text: self.text, width: Some(self.lexeme.len()), - kind: kind, + kind, } } } diff --git a/tests/integration.rs b/tests/integration.rs index 9a18142..e98890e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -51,9 +51,14 @@ fn integration_test( let tmp = TempDir::new("just-integration") .unwrap_or_else( |err| panic!("integration test: failed to create temporary directory: {}", err)); - let mut path = tmp.path().to_path_buf(); - path.push("justfile"); - brev::dump(path, justfile); + + let mut justfile_path = tmp.path().to_path_buf(); + justfile_path.push("justfile"); + brev::dump(justfile_path, justfile); + + let mut dotenv_path = tmp.path().to_path_buf(); + dotenv_path.push(".env"); + brev::dump(dotenv_path, "DOTENV_KEY=dotenv-value"); let output = process::Command::new(&executable_path("just")) .current_dir(tmp.path()) @@ -1739,3 +1744,30 @@ echo: stderr: "echo 1\n", status: EXIT_SUCCESS, } + +integration_test! { + name: dotenv_variable_in_recipe, + justfile: " +# +echo: + echo $DOTENV_KEY + ", + args: (), + stdout: "dotenv-value\n", + stderr: "echo $DOTENV_KEY\n", + status: EXIT_SUCCESS, +} + +integration_test! { + name: dotenv_variable_in_backtick, + justfile: " +# +X=`echo $DOTENV_KEY` +echo: + echo {{X}} + ", + args: (), + stdout: "dotenv-value\n", + stderr: "echo dotenv-value\n", + status: EXIT_SUCCESS, +}