Wait for child processes to finish (#345)
Thanks to @bheisler for the feature request and initial implementation. Fixes #302
This commit is contained in:
parent
c615a3fb0b
commit
b14d1ec97c
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Cargo.lock linguist-generated diff=nodiff
|
81
Cargo.lock
generated
81
Cargo.lock
generated
@ -88,6 +88,15 @@ dependencies = [
|
|||||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctrlc"
|
||||||
|
version = "3.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenv"
|
name = "dotenv"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@ -108,6 +117,18 @@ name = "either"
|
|||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.5.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "executable-path"
|
name = "executable-path"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -151,6 +172,14 @@ name = "glob"
|
|||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
@ -168,12 +197,15 @@ dependencies = [
|
|||||||
"atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"brev 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"brev 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"env_logger 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"executable-path 1.0.0 (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.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"target 1.0.0 (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.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -190,6 +222,14 @@ name = "libc"
|
|||||||
version = "0.2.42"
|
version = "0.2.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@ -198,6 +238,23 @@ dependencies = [
|
|||||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
@ -305,6 +362,14 @@ dependencies = [
|
|||||||
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"wincolor 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termion"
|
name = "termion"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
@ -389,6 +454,14 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wincolor"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
||||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||||
@ -401,19 +474,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
"checksum cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "49ec142f5768efb5b7622aebc3fdbdbb8950a4b9ba996393cb76ef7466e8747d"
|
"checksum cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "49ec142f5768efb5b7622aebc3fdbdbb8950a4b9ba996393cb76ef7466e8747d"
|
||||||
"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"
|
"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"
|
||||||
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
|
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
|
||||||
|
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
|
||||||
"checksum dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d0a1279c96732bc6800ce6337b6a614697b0e74ae058dc03c62ebeb78b4d86"
|
"checksum dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d0a1279c96732bc6800ce6337b6a614697b0e74ae058dc03c62ebeb78b4d86"
|
||||||
"checksum edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3bd26878c3d921f89797a4e1a1711919f999a9f6946bb6f5a4ffda126d297b7e"
|
"checksum edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3bd26878c3d921f89797a4e1a1711919f999a9f6946bb6f5a4ffda126d297b7e"
|
||||||
"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0"
|
"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0"
|
||||||
|
"checksum env_logger 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)" = "f4d7e69c283751083d53d01eac767407343b8b69c4bd70058e08adc2637cb257"
|
||||||
"checksum executable-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ebc5a6d89e3c90b84e8f33c8737933dda8f1c106b5415900b38b9d433841478"
|
"checksum executable-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ebc5a6d89e3c90b84e8f33c8737933dda8f1c106b5415900b38b9d433841478"
|
||||||
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
|
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
|
||||||
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
|
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
|
||||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
"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 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 glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||||
|
"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
|
||||||
"checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450"
|
"checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450"
|
||||||
"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739"
|
"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739"
|
||||||
"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1"
|
"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1"
|
||||||
|
"checksum log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cba860f648db8e6f269df990180c2217f333472b4a6e901e97446858487971e2"
|
||||||
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
||||||
|
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
|
||||||
|
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
"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 rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
||||||
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
|
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
|
||||||
@ -428,6 +507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
||||||
"checksum target 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10000465bb0cc031c87a44668991b284fd84c0e6bd945f62d4af04e9e52a222a"
|
"checksum target 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10000465bb0cc031c87a44668991b284fd84c0e6bd945f62d4af04e9e52a222a"
|
||||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||||
|
"checksum termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "722426c4a0539da2c4ffd9b419d90ad540b4cff4a053be9069c908d4d07e2836"
|
||||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
"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 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 thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
||||||
@ -441,3 +521,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
|
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
|
||||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
"checksum wincolor 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9dc3aa9dcda98b5a16150c54619c1ead22e3d3a5d458778ae914be760aa981a"
|
||||||
|
@ -15,13 +15,19 @@ brev = "0.1.6"
|
|||||||
clap = "2.0.0"
|
clap = "2.0.0"
|
||||||
dotenv = "0.13.0"
|
dotenv = "0.13.0"
|
||||||
edit-distance = "2.0.0"
|
edit-distance = "2.0.0"
|
||||||
|
env_logger = "0.5.12"
|
||||||
itertools = "0.7"
|
itertools = "0.7"
|
||||||
lazy_static = "1.0.0"
|
lazy_static = "1.0.0"
|
||||||
libc = "0.2.21"
|
libc = "0.2.21"
|
||||||
|
log = "0.4.4"
|
||||||
regex = "1.0.0"
|
regex = "1.0.0"
|
||||||
target = "1.0.0"
|
target = "1.0.0"
|
||||||
tempdir = "0.3.5"
|
tempdir = "0.3.5"
|
||||||
unicode-width = "0.1.3"
|
unicode-width = "0.1.3"
|
||||||
|
|
||||||
|
[dependencies.ctrlc]
|
||||||
|
version = "3.1"
|
||||||
|
features = ['termination']
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
executable-path = "1.0.0"
|
executable-path = "1.0.0"
|
||||||
|
2
justfile
2
justfile
@ -27,7 +27,7 @@ check:
|
|||||||
cargo check
|
cargo check
|
||||||
|
|
||||||
watch COMMAND='test':
|
watch COMMAND='test':
|
||||||
cargo watch {{COMMAND}}
|
cargo watch --clear --exec {{COMMAND}}
|
||||||
|
|
||||||
version = `sed -En 's/version[[:space:]]*=[[:space:]]*"([^"]+)"/v\1/p' Cargo.toml`
|
version = `sed -En 's/version[[:space:]]*=[[:space:]]*"([^"]+)"/v\1/p' Cargo.toml`
|
||||||
|
|
||||||
|
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
tab_spaces = 2
|
||||||
|
max_width = 100
|
@ -1,5 +1,3 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use common::*;
|
use common::*;
|
||||||
|
|
||||||
use brev;
|
use brev;
|
||||||
@ -154,8 +152,9 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
|
|||||||
process::Stdio::inherit()
|
process::Stdio::inherit()
|
||||||
});
|
});
|
||||||
|
|
||||||
brev::output(cmd)
|
InterruptHandler::guard(|| brev::output(cmd)
|
||||||
.map_err(|output_error| RuntimeError::Backtick{token: token.clone(), output_error})
|
.map_err(|output_error| RuntimeError::Backtick{token: token.clone(), output_error})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ pub use std::io::prelude::*;
|
|||||||
pub use std::ops::Range;
|
pub use std::ops::Range;
|
||||||
pub use std::path::{Path, PathBuf};
|
pub use std::path::{Path, PathBuf};
|
||||||
pub use std::process::Command;
|
pub use std::process::Command;
|
||||||
|
pub use std::sync::{Mutex, MutexGuard};
|
||||||
pub use std::{cmp, env, fs, fmt, io, iter, process, vec, usize};
|
pub use std::{cmp, env, fs, fmt, io, iter, process, vec, usize};
|
||||||
|
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
@ -21,6 +22,7 @@ pub use cooked_string::CookedString;
|
|||||||
pub use expression::Expression;
|
pub use expression::Expression;
|
||||||
pub use fragment::Fragment;
|
pub use fragment::Fragment;
|
||||||
pub use function::{evaluate_function, resolve_function, FunctionContext};
|
pub use function::{evaluate_function, resolve_function, FunctionContext};
|
||||||
|
pub use interrupt_handler::InterruptHandler;
|
||||||
pub use justfile::Justfile;
|
pub use justfile::Justfile;
|
||||||
pub use lexer::Lexer;
|
pub use lexer::Lexer;
|
||||||
pub use load_dotenv::load_dotenv;
|
pub use load_dotenv::load_dotenv;
|
||||||
|
7
src/die.rs
Normal file
7
src/die.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
macro_rules! die {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
extern crate std;
|
||||||
|
eprintln!($($arg)*);
|
||||||
|
process::exit(EXIT_FAILURE)
|
||||||
|
}};
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use common::*;
|
use common::*;
|
||||||
|
|
||||||
use target;
|
use target;
|
||||||
|
|
||||||
use platform::{Platform, PlatformInterface};
|
use platform::{Platform, PlatformInterface};
|
||||||
@ -106,14 +105,14 @@ pub fn invocation_directory(context: &FunctionContext) -> Result<String, String>
|
|||||||
|
|
||||||
pub fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
|
pub fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
|
||||||
use std::env::VarError::*;
|
use std::env::VarError::*;
|
||||||
|
|
||||||
if let Some(value) = context.dotenv.get(key) {
|
if let Some(value) = context.dotenv.get(key) {
|
||||||
return Ok(value.clone());
|
return Ok(value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
match env::var(key) {
|
match env::var(key) {
|
||||||
Err(NotPresent) => Err(format!("environment variable `{}` not present", key)),
|
Err(NotPresent) => Err(format!("environment variable `{}` not present", key)),
|
||||||
Err(NotUnicode(os_string)) =>
|
Err(NotUnicode(os_string)) =>
|
||||||
Err(format!("environment variable `{}` not unicode: {:?}", key, os_string)),
|
Err(format!("environment variable `{}` not unicode: {:?}", key, os_string)),
|
||||||
Ok(value) => Ok(value),
|
Ok(value) => Ok(value),
|
||||||
}
|
}
|
||||||
@ -131,7 +130,7 @@ pub fn env_var_or_default(
|
|||||||
use std::env::VarError::*;
|
use std::env::VarError::*;
|
||||||
match env::var(key) {
|
match env::var(key) {
|
||||||
Err(NotPresent) => Ok(default.to_string()),
|
Err(NotPresent) => Ok(default.to_string()),
|
||||||
Err(NotUnicode(os_string)) =>
|
Err(NotUnicode(os_string)) =>
|
||||||
Err(format!("environment variable `{}` not unicode: {:?}", key, os_string)),
|
Err(format!("environment variable `{}` not unicode: {:?}", key, os_string)),
|
||||||
Ok(value) => Ok(value),
|
Ok(value) => Ok(value),
|
||||||
}
|
}
|
||||||
|
93
src/interrupt_handler.rs
Normal file
93
src/interrupt_handler.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use common::*;
|
||||||
|
|
||||||
|
use ctrlc;
|
||||||
|
|
||||||
|
pub struct InterruptHandler {
|
||||||
|
blocks: u32,
|
||||||
|
interrupted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterruptHandler {
|
||||||
|
pub fn install() -> Result<(), ctrlc::Error> {
|
||||||
|
ctrlc::set_handler(|| InterruptHandler::instance().interrupt())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance() -> MutexGuard<'static, InterruptHandler> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref INSTANCE: Mutex<InterruptHandler> = Mutex::new(InterruptHandler::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
match INSTANCE.lock() {
|
||||||
|
Ok(guard) => guard,
|
||||||
|
Err(poison_error) => die!(
|
||||||
|
"{}",
|
||||||
|
RuntimeError::Internal {
|
||||||
|
message: format!("interrupt handler mutex poisoned: {}", poison_error),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new() -> InterruptHandler {
|
||||||
|
InterruptHandler {
|
||||||
|
blocks: 0,
|
||||||
|
interrupted: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interrupt(&mut self) {
|
||||||
|
self.interrupted = true;
|
||||||
|
|
||||||
|
if self.blocks > 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit() {
|
||||||
|
process::exit(130);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block(&mut self) {
|
||||||
|
self.blocks += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unblock(&mut self) {
|
||||||
|
if self.blocks == 0 {
|
||||||
|
die!(
|
||||||
|
"{}",
|
||||||
|
RuntimeError::Internal {
|
||||||
|
message: "attempted to unblock interrupt handler, but handler was not blocked"
|
||||||
|
.to_string(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.blocks -= 1;
|
||||||
|
|
||||||
|
if self.interrupted {
|
||||||
|
Self::exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn guard<T, F: FnOnce() -> T>(function: F) -> T {
|
||||||
|
let _guard = InterruptGuard::new();
|
||||||
|
function()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InterruptGuard;
|
||||||
|
|
||||||
|
impl InterruptGuard {
|
||||||
|
fn new() -> InterruptGuard {
|
||||||
|
InterruptHandler::instance().block();
|
||||||
|
InterruptGuard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for InterruptGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
InterruptHandler::instance().unblock();
|
||||||
|
}
|
||||||
|
}
|
219
src/justfile.rs
219
src/justfile.rs
@ -1,16 +1,17 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use common::*;
|
use common::*;
|
||||||
|
|
||||||
use edit_distance::edit_distance;
|
use edit_distance::edit_distance;
|
||||||
|
|
||||||
pub struct Justfile<'a> {
|
pub struct Justfile<'a> {
|
||||||
pub recipes: Map<&'a str, Recipe<'a>>,
|
pub recipes: Map<&'a str, Recipe<'a>>,
|
||||||
pub assignments: Map<&'a str, Expression<'a>>,
|
pub assignments: Map<&'a str, Expression<'a>>,
|
||||||
pub exports: Set<&'a str>,
|
pub exports: Set<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
impl<'a, 'b> Justfile<'a>
|
||||||
|
where
|
||||||
|
'a: 'b,
|
||||||
|
{
|
||||||
pub fn first(&self) -> Option<&Recipe> {
|
pub fn first(&self) -> Option<&Recipe> {
|
||||||
let mut first: Option<&Recipe> = None;
|
let mut first: Option<&Recipe> = None;
|
||||||
for recipe in self.recipes.values() {
|
for recipe in self.recipes.values() {
|
||||||
@ -30,13 +31,15 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn suggest(&self, name: &str) -> Option<&'a str> {
|
pub fn suggest(&self, name: &str) -> Option<&'a str> {
|
||||||
let mut suggestions = self.recipes.keys()
|
let mut suggestions = self
|
||||||
|
.recipes
|
||||||
|
.keys()
|
||||||
.map(|suggestion| (edit_distance(suggestion, name), suggestion))
|
.map(|suggestion| (edit_distance(suggestion, name), suggestion))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
suggestions.sort();
|
suggestions.sort();
|
||||||
if let Some(&(distance, suggestion)) = suggestions.first() {
|
if let Some(&(distance, suggestion)) = suggestions.first() {
|
||||||
if distance < 3 {
|
if distance < 3 {
|
||||||
return Some(suggestion)
|
return Some(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -45,15 +48,20 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
pub fn run(
|
pub fn run(
|
||||||
&'a self,
|
&'a self,
|
||||||
invocation_directory: Result<PathBuf, String>,
|
invocation_directory: Result<PathBuf, String>,
|
||||||
arguments: &[&'a str],
|
arguments: &[&'a str],
|
||||||
configuration: &Configuration<'a>,
|
configuration: &Configuration<'a>,
|
||||||
) -> RunResult<'a, ()> {
|
) -> RunResult<'a, ()> {
|
||||||
let unknown_overrides = configuration.overrides.keys().cloned()
|
let unknown_overrides = configuration
|
||||||
|
.overrides
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
.filter(|name| !self.assignments.contains_key(name))
|
.filter(|name| !self.assignments.contains_key(name))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !unknown_overrides.is_empty() {
|
if !unknown_overrides.is_empty() {
|
||||||
return Err(RuntimeError::UnknownOverrides{overrides: unknown_overrides});
|
return Err(RuntimeError::UnknownOverrides {
|
||||||
|
overrides: unknown_overrides,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let dotenv = load_dotenv()?;
|
let dotenv = load_dotenv()?;
|
||||||
@ -82,7 +90,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
|
|
||||||
let mut missing = vec![];
|
let mut missing = vec![];
|
||||||
let mut grouped = vec![];
|
let mut grouped = vec![];
|
||||||
let mut rest = arguments;
|
let mut rest = arguments;
|
||||||
|
|
||||||
while let Some((argument, mut tail)) = rest.split_first() {
|
while let Some((argument, mut tail)) = rest.split_first() {
|
||||||
if let Some(recipe) = self.recipes.get(argument) {
|
if let Some(recipe) = self.recipes.get(argument) {
|
||||||
@ -94,9 +102,9 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
if !argument_range.range_contains(argument_count) {
|
if !argument_range.range_contains(argument_count) {
|
||||||
return Err(RuntimeError::ArgumentCountMismatch {
|
return Err(RuntimeError::ArgumentCountMismatch {
|
||||||
recipe: recipe.name,
|
recipe: recipe.name,
|
||||||
found: tail.len(),
|
found: tail.len(),
|
||||||
min: recipe.min_arguments(),
|
min: recipe.min_arguments(),
|
||||||
max: recipe.max_arguments(),
|
max: recipe.max_arguments(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
grouped.push((recipe, &tail[0..argument_count]));
|
grouped.push((recipe, &tail[0..argument_count]));
|
||||||
@ -114,12 +122,23 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
return Err(RuntimeError::UnknownRecipes{recipes: missing, suggestion});
|
return Err(RuntimeError::UnknownRecipes {
|
||||||
|
recipes: missing,
|
||||||
|
suggestion,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ran = empty();
|
let mut ran = empty();
|
||||||
for (recipe, arguments) in grouped {
|
for (recipe, arguments) in grouped {
|
||||||
self.run_recipe(&invocation_directory, recipe, arguments, &scope, &dotenv, configuration, &mut ran)?
|
self.run_recipe(
|
||||||
|
&invocation_directory,
|
||||||
|
recipe,
|
||||||
|
arguments,
|
||||||
|
&scope,
|
||||||
|
&dotenv,
|
||||||
|
configuration,
|
||||||
|
&mut ran,
|
||||||
|
)?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -128,19 +147,34 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
fn run_recipe<'c>(
|
fn run_recipe<'c>(
|
||||||
&'c self,
|
&'c self,
|
||||||
invocation_directory: &Result<PathBuf, String>,
|
invocation_directory: &Result<PathBuf, String>,
|
||||||
recipe: &Recipe<'a>,
|
recipe: &Recipe<'a>,
|
||||||
arguments: &[&'a str],
|
arguments: &[&'a str],
|
||||||
scope: &Map<&'c str, String>,
|
scope: &Map<&'c str, String>,
|
||||||
dotenv: &Map<String, String>,
|
dotenv: &Map<String, String>,
|
||||||
configuration: &Configuration<'a>,
|
configuration: &Configuration<'a>,
|
||||||
ran: &mut Set<&'a str>,
|
ran: &mut Set<&'a str>,
|
||||||
) -> RunResult<()> {
|
) -> RunResult<()> {
|
||||||
for dependency_name in &recipe.dependencies {
|
for dependency_name in &recipe.dependencies {
|
||||||
if !ran.contains(dependency_name) {
|
if !ran.contains(dependency_name) {
|
||||||
self.run_recipe(invocation_directory, &self.recipes[dependency_name], &[], scope, dotenv, configuration, ran)?;
|
self.run_recipe(
|
||||||
|
invocation_directory,
|
||||||
|
&self.recipes[dependency_name],
|
||||||
|
&[],
|
||||||
|
scope,
|
||||||
|
dotenv,
|
||||||
|
configuration,
|
||||||
|
ran,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recipe.run(invocation_directory, arguments, scope, dotenv, &self.exports, configuration)?;
|
recipe.run(
|
||||||
|
invocation_directory,
|
||||||
|
arguments,
|
||||||
|
scope,
|
||||||
|
dotenv,
|
||||||
|
&self.exports,
|
||||||
|
configuration,
|
||||||
|
)?;
|
||||||
ran.insert(recipe.name);
|
ran.insert(recipe.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -182,8 +216,14 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_recipes() {
|
fn unknown_recipes() {
|
||||||
match parse_success("a:\nb:\nc:").run(no_cwd_err(), &["a", "x", "y", "z"], &Default::default()).unwrap_err() {
|
match parse_success("a:\nb:\nc:")
|
||||||
UnknownRecipes{recipes, suggestion} => {
|
.run(no_cwd_err(), &["a", "x", "y", "z"], &Default::default())
|
||||||
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
UnknownRecipes {
|
||||||
|
recipes,
|
||||||
|
suggestion,
|
||||||
|
} => {
|
||||||
assert_eq!(recipes, &["x", "y", "z"]);
|
assert_eq!(recipes, &["x", "y", "z"]);
|
||||||
assert_eq!(suggestion, None);
|
assert_eq!(suggestion, None);
|
||||||
}
|
}
|
||||||
@ -191,7 +231,6 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_shebang() {
|
fn run_shebang() {
|
||||||
// this test exists to make sure that shebang recipes
|
// this test exists to make sure that shebang recipes
|
||||||
@ -210,12 +249,19 @@ a:
|
|||||||
x
|
x
|
||||||
";
|
";
|
||||||
|
|
||||||
match parse_success(text).run(no_cwd_err(), &["a"], &Default::default()).unwrap_err() {
|
match parse_success(text)
|
||||||
Code{recipe, line_number, code} => {
|
.run(no_cwd_err(), &["a"], &Default::default())
|
||||||
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
Code {
|
||||||
|
recipe,
|
||||||
|
line_number,
|
||||||
|
code,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(code, 200);
|
assert_eq!(code, 200);
|
||||||
assert_eq!(line_number, None);
|
assert_eq!(line_number, None);
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,12 +269,18 @@ a:
|
|||||||
#[test]
|
#[test]
|
||||||
fn code_error() {
|
fn code_error() {
|
||||||
match parse_success("fail:\n @exit 100")
|
match parse_success("fail:\n @exit 100")
|
||||||
.run(no_cwd_err(), &["fail"], &Default::default()).unwrap_err() {
|
.run(no_cwd_err(), &["fail"], &Default::default())
|
||||||
Code{recipe, line_number, code} => {
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
Code {
|
||||||
|
recipe,
|
||||||
|
line_number,
|
||||||
|
code,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "fail");
|
assert_eq!(recipe, "fail");
|
||||||
assert_eq!(code, 100);
|
assert_eq!(code, 100);
|
||||||
assert_eq!(line_number, Some(2));
|
assert_eq!(line_number, Some(2));
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,38 +291,61 @@ a:
|
|||||||
a return code:
|
a return code:
|
||||||
@x() { {{return}} {{code + "0"}}; }; x"#;
|
@x() { {{return}} {{code + "0"}}; }; x"#;
|
||||||
|
|
||||||
match parse_success(text).run(no_cwd_err(), &["a", "return", "15"], &Default::default()).unwrap_err() {
|
match parse_success(text)
|
||||||
Code{recipe, line_number, code} => {
|
.run(no_cwd_err(), &["a", "return", "15"], &Default::default())
|
||||||
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
Code {
|
||||||
|
recipe,
|
||||||
|
line_number,
|
||||||
|
code,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(code, 150);
|
assert_eq!(code, 150);
|
||||||
assert_eq!(line_number, Some(3));
|
assert_eq!(line_number, Some(3));
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_some_arguments() {
|
fn missing_some_arguments() {
|
||||||
match parse_success("a b c d:").run(no_cwd_err(), &["a", "b", "c"], &Default::default()).unwrap_err() {
|
match parse_success("a b c d:")
|
||||||
ArgumentCountMismatch{recipe, found, min, max} => {
|
.run(no_cwd_err(), &["a", "b", "c"], &Default::default())
|
||||||
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
ArgumentCountMismatch {
|
||||||
|
recipe,
|
||||||
|
found,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 2);
|
assert_eq!(found, 2);
|
||||||
assert_eq!(min, 3);
|
assert_eq!(min, 3);
|
||||||
assert_eq!(max, 3);
|
assert_eq!(max, 3);
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_some_arguments_variadic() {
|
fn missing_some_arguments_variadic() {
|
||||||
match parse_success("a b c +d:").run(no_cwd_err(), &["a", "B", "C"], &Default::default()).unwrap_err() {
|
match parse_success("a b c +d:")
|
||||||
ArgumentCountMismatch{recipe, found, min, max} => {
|
.run(no_cwd_err(), &["a", "B", "C"], &Default::default())
|
||||||
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
ArgumentCountMismatch {
|
||||||
|
recipe,
|
||||||
|
found,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 2);
|
assert_eq!(found, 2);
|
||||||
assert_eq!(min, 3);
|
assert_eq!(min, 3);
|
||||||
assert_eq!(max, usize::MAX - 1);
|
assert_eq!(max, usize::MAX - 1);
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -278,39 +353,62 @@ a return code:
|
|||||||
#[test]
|
#[test]
|
||||||
fn missing_all_arguments() {
|
fn missing_all_arguments() {
|
||||||
match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}")
|
match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}")
|
||||||
.run(no_cwd_err(), &["a"], &Default::default()).unwrap_err() {
|
.run(no_cwd_err(), &["a"], &Default::default())
|
||||||
ArgumentCountMismatch{recipe, found, min, max} => {
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
ArgumentCountMismatch {
|
||||||
|
recipe,
|
||||||
|
found,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 0);
|
assert_eq!(found, 0);
|
||||||
assert_eq!(min, 3);
|
assert_eq!(min, 3);
|
||||||
assert_eq!(max, 3);
|
assert_eq!(max, 3);
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_some_defaults() {
|
fn missing_some_defaults() {
|
||||||
match parse_success("a b c d='hello':").run(no_cwd_err(), &["a", "b"], &Default::default()).unwrap_err() {
|
match parse_success("a b c d='hello':")
|
||||||
ArgumentCountMismatch{recipe, found, min, max} => {
|
.run(no_cwd_err(), &["a", "b"], &Default::default())
|
||||||
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
ArgumentCountMismatch {
|
||||||
|
recipe,
|
||||||
|
found,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 1);
|
assert_eq!(found, 1);
|
||||||
assert_eq!(min, 2);
|
assert_eq!(min, 2);
|
||||||
assert_eq!(max, 3);
|
assert_eq!(max, 3);
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_all_defaults() {
|
fn missing_all_defaults() {
|
||||||
match parse_success("a b c='r' d='h':").run(no_cwd_err(), &["a"], &Default::default()).unwrap_err() {
|
match parse_success("a b c='r' d='h':")
|
||||||
ArgumentCountMismatch{recipe, found, min, max} => {
|
.run(no_cwd_err(), &["a"], &Default::default())
|
||||||
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
ArgumentCountMismatch {
|
||||||
|
recipe,
|
||||||
|
found,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 0);
|
assert_eq!(found, 0);
|
||||||
assert_eq!(min, 1);
|
assert_eq!(min, 1);
|
||||||
assert_eq!(max, 3);
|
assert_eq!(max, 3);
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,10 +419,12 @@ a return code:
|
|||||||
configuration.overrides.insert("foo", "bar");
|
configuration.overrides.insert("foo", "bar");
|
||||||
configuration.overrides.insert("baz", "bob");
|
configuration.overrides.insert("baz", "bob");
|
||||||
match parse_success("a:\n echo {{`f() { return 100; }; f`}}")
|
match parse_success("a:\n echo {{`f() { return 100; }; f`}}")
|
||||||
.run(no_cwd_err(), &["a"], &configuration).unwrap_err() {
|
.run(no_cwd_err(), &["a"], &configuration)
|
||||||
UnknownOverrides{overrides} => {
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
UnknownOverrides { overrides } => {
|
||||||
assert_eq!(overrides, &["baz", "foo"]);
|
assert_eq!(overrides, &["baz", "foo"]);
|
||||||
},
|
}
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -346,11 +446,18 @@ wut:
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
match parse_success(text).run(no_cwd_err(), &["wut"], &configuration).unwrap_err() {
|
match parse_success(text)
|
||||||
Code{code: _, line_number, recipe} => {
|
.run(no_cwd_err(), &["wut"], &configuration)
|
||||||
|
.unwrap_err()
|
||||||
|
{
|
||||||
|
Code {
|
||||||
|
code: _,
|
||||||
|
line_number,
|
||||||
|
recipe,
|
||||||
|
} => {
|
||||||
assert_eq!(recipe, "wut");
|
assert_eq!(recipe, "wut");
|
||||||
assert_eq!(line_number, Some(8));
|
assert_eq!(line_number, Some(8));
|
||||||
},
|
}
|
||||||
other => panic!("expected a recipe code errror, but got: {}", other),
|
other => panic!("expected a recipe code errror, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
src/main.rs
11
src/main.rs
@ -1,10 +1,13 @@
|
|||||||
#[macro_use]
|
#[macro_use] extern crate lazy_static;
|
||||||
extern crate lazy_static;
|
#[macro_use] extern crate log;
|
||||||
|
|
||||||
extern crate ansi_term;
|
extern crate ansi_term;
|
||||||
extern crate brev;
|
extern crate brev;
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
extern crate ctrlc;
|
||||||
extern crate dotenv;
|
extern crate dotenv;
|
||||||
extern crate edit_distance;
|
extern crate edit_distance;
|
||||||
|
extern crate env_logger;
|
||||||
extern crate itertools;
|
extern crate itertools;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
@ -16,6 +19,9 @@ extern crate unicode_width;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod testing;
|
mod testing;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod die;
|
||||||
|
|
||||||
mod assignment_evaluator;
|
mod assignment_evaluator;
|
||||||
mod assignment_resolver;
|
mod assignment_resolver;
|
||||||
mod color;
|
mod color;
|
||||||
@ -41,6 +47,7 @@ mod run;
|
|||||||
mod runtime_error;
|
mod runtime_error;
|
||||||
mod shebang;
|
mod shebang;
|
||||||
mod token;
|
mod token;
|
||||||
|
mod interrupt_handler;
|
||||||
|
|
||||||
use common::*;
|
use common::*;
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use common::*;
|
use common::*;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::{ExitStatus, Command, Stdio};
|
use std::process::{ExitStatus, Command, Stdio};
|
||||||
|
|
||||||
use platform::{Platform, PlatformInterface};
|
use platform::{Platform, PlatformInterface};
|
||||||
@ -161,7 +160,7 @@ impl<'a> Recipe<'a> {
|
|||||||
command.export_environment_variables(scope, dotenv, exports)?;
|
command.export_environment_variables(scope, dotenv, exports)?;
|
||||||
|
|
||||||
// run it!
|
// run it!
|
||||||
match command.status() {
|
match InterruptHandler::guard(|| command.status()) {
|
||||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
return Err(RuntimeError::Code{recipe: self.name, line_number: None, code})
|
return Err(RuntimeError::Code{recipe: self.name, line_number: None, code})
|
||||||
@ -235,7 +234,7 @@ impl<'a> Recipe<'a> {
|
|||||||
|
|
||||||
cmd.export_environment_variables(scope, dotenv, exports)?;
|
cmd.export_environment_variables(scope, dotenv, exports)?;
|
||||||
|
|
||||||
match cmd.status() {
|
match InterruptHandler::guard(|| cmd.status()) {
|
||||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
return Err(RuntimeError::Code{
|
return Err(RuntimeError::Code{
|
||||||
|
20
src/run.rs
20
src/run.rs
@ -1,22 +1,16 @@
|
|||||||
use common::*;
|
use common::*;
|
||||||
|
|
||||||
use std::{convert, ffi, cmp};
|
use std::{convert, ffi};
|
||||||
use clap::{App, Arg, ArgGroup, AppSettings};
|
use clap::{App, Arg, ArgGroup, AppSettings};
|
||||||
use configuration::DEFAULT_SHELL;
|
use configuration::DEFAULT_SHELL;
|
||||||
use misc::maybe_s;
|
use misc::maybe_s;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
use env_logger;
|
||||||
|
use interrupt_handler::InterruptHandler;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use ansi_term::enable_ansi_support;
|
use ansi_term::enable_ansi_support;
|
||||||
|
|
||||||
macro_rules! die {
|
|
||||||
($($arg:tt)*) => {{
|
|
||||||
extern crate std;
|
|
||||||
eprintln!($($arg)*);
|
|
||||||
process::exit(EXIT_FAILURE)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn edit<P: convert::AsRef<ffi::OsStr>>(path: P) -> ! {
|
fn edit<P: convert::AsRef<ffi::OsStr>>(path: P) -> ! {
|
||||||
let editor = env::var_os("EDITOR")
|
let editor = env::var_os("EDITOR")
|
||||||
.unwrap_or_else(|| die!("Error getting EDITOR environment variable"));
|
.unwrap_or_else(|| die!("Error getting EDITOR environment variable"));
|
||||||
@ -47,6 +41,10 @@ pub fn run() {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
enable_ansi_support().ok();
|
enable_ansi_support().ok();
|
||||||
|
|
||||||
|
env_logger::Builder::from_env(
|
||||||
|
env_logger::Env::new().filter("JUST_LOG").write_style("JUST_LOG_STYLE")
|
||||||
|
).init();
|
||||||
|
|
||||||
let invocation_directory = env::current_dir()
|
let invocation_directory = env::current_dir()
|
||||||
.map_err(|e| format!("Error getting current directory: {}", e));
|
.map_err(|e| format!("Error getting current directory: {}", e));
|
||||||
|
|
||||||
@ -357,6 +355,10 @@ pub fn run() {
|
|||||||
overrides,
|
overrides,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Err(error) = InterruptHandler::install() {
|
||||||
|
warn!("Failed to set CTRL-C handler: {}", error)
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(run_error) = justfile.run(
|
if let Err(run_error) = justfile.run(
|
||||||
invocation_directory,
|
invocation_directory,
|
||||||
&arguments,
|
&arguments,
|
||||||
|
81
tests/interrupts.rs
Normal file
81
tests/interrupts.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
extern crate brev;
|
||||||
|
extern crate executable_path;
|
||||||
|
extern crate libc;
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
|
use executable_path::executable_path;
|
||||||
|
use tempdir::TempDir;
|
||||||
|
use std::{process::Command, time::{Duration, Instant}, thread};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn kill(process_id: u32) {
|
||||||
|
unsafe {
|
||||||
|
libc::kill(process_id as i32, libc::SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn interrupt_test(justfile: &str) {
|
||||||
|
let tmp = TempDir::new("just-interrupts")
|
||||||
|
.unwrap_or_else(
|
||||||
|
|err| panic!("integration test: failed to create temporary directory: {}", err));
|
||||||
|
|
||||||
|
let mut justfile_path = tmp.path().to_path_buf();
|
||||||
|
justfile_path.push("justfile");
|
||||||
|
brev::dump(justfile_path, justfile);
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
let mut child = Command::new(&executable_path("just"))
|
||||||
|
.current_dir(&tmp)
|
||||||
|
.spawn()
|
||||||
|
.expect("just invocation failed");
|
||||||
|
|
||||||
|
thread::sleep(Duration::new(1, 0));
|
||||||
|
|
||||||
|
kill(child.id());
|
||||||
|
|
||||||
|
let status = child.wait().unwrap();
|
||||||
|
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
|
if elapsed > Duration::new(2, 500_000_000) {
|
||||||
|
panic!("process returned too late: {:?}", elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if elapsed < Duration::new(1, 500_000_000) {
|
||||||
|
panic!("process returned too early : {:?}", elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(status.code(), Some(130));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn interrupt_shebang() {
|
||||||
|
interrupt_test("
|
||||||
|
default:
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
sleep 2
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn interrupt_line() {
|
||||||
|
interrupt_test("
|
||||||
|
default:
|
||||||
|
@sleep 2
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn interrupt_backtick() {
|
||||||
|
interrupt_test("
|
||||||
|
foo = `sleep 2`
|
||||||
|
|
||||||
|
default:
|
||||||
|
@echo hello
|
||||||
|
");
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
extern crate brev;
|
extern crate brev;
|
||||||
extern crate executable_path;
|
extern crate executable_path;
|
||||||
extern crate libc;
|
|
||||||
extern crate target;
|
extern crate target;
|
||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user