Repository: wickedchicken/checklist (language: rust)

Issue:
Execute scripts
Directly execute commands instead of printing them for the user.

https://crates.io/crates/duct
https://docs.rs/duct/0.12.0/duct/


Discussion / hints:

Fix patch:
diff --git a/.checklist.yml b/.checklist.yml
index 6e4d755..74097c4 100644
--- a/.checklist.yml
+++ b/.checklist.yml
@@ -1,8 +1,12 @@
 ---
-schema_version: 1
+schema_version: 2
 committing:
-  - run cargo fmt
-  - run cargo build
-  - run cargo clippy
-  - run cargo test
-  - run yamllint -s .checklist.yml .travis.yml
+  manual:
+    - checked that the version number has been bumped
+    - checked that the schema version number has been bumped
+  automated:
+    - cargo fmt --all -- --check
+    - cargo build
+    - cargo clippy
+    - cargo test
+    - yamllint -s .checklist.yml .travis.yml
diff --git a/Cargo.lock b/Cargo.lock
index 0fbc42e..1fbdd01 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -96,13 +96,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "checklist"
-version = "0.1.1"
+version = "0.2.0"
 dependencies = [
  "assert_cmd 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "assert_fs 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "console 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "dialoguer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "duct_sh 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rexpect 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -201,6 +203,25 @@ name = "dtoa"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "duct"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "os_pipe 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "shared_child 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "duct_sh"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "duct 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "encode_unicode"
 version = "0.3.5"
@@ -309,6 +330,18 @@ dependencies = [
  "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "indicatif"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "console 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.3"
@@ -328,6 +361,11 @@ name = "lazy_static"
 version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "lazycell"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "libc"
 version = "0.2.50"
@@ -376,6 +414,18 @@ dependencies = [
  "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "nix"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "normalize-line-endings"
 version = "0.2.2"
@@ -386,6 +436,23 @@ name = "num-traits"
 version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "number_prefix"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "os_pipe"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "owning_ref"
 version = "0.4.0"
@@ -742,6 +809,15 @@ dependencies = [
  "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "shared_child"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "smallvec"
 version = "0.6.9"
@@ -974,6 +1050,8 @@ dependencies = [
 "checksum dialoguer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ad1c29a0368928e78c551354dbff79f103a962ad820519724ef0d74f1c62fa9"
 "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd"
+"checksum duct 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3640af123c78bedc20c1d3928e43cc0621e57011899d1ef917900c12fdb7a1ee"
+"checksum duct_sh 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48ead067afea0562fc6df304a1d5d62de0244bad0e600c70eb1e55f52a401318"
 "checksum encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd"
 "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
 "checksum escargot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ceb9adbf9874d5d028b5e4c5739d22b71988252b25c9c98fe7cf9738bee84597"
@@ -986,18 +1064,23 @@ dependencies = [
 "checksum globwalk 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "89fa2e29859da05acd066bd45996f05c271b271d7ec4a781f909682328f65d25"
 "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
 "checksum ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ad03ca67dc12474ecd91fdb94d758cbd20cb4e7a78ebe831df26a9b7511e1162"
+"checksum indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2c60da1c9abea75996b70a931bba6c750730399005b61ccd853cee50ef3d0d0c"
 "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
 "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
+"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
 "checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1"
 "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e"
 "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
 "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
 "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43"
 "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
+"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b"
 "checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32"
 "checksum normalize-line-endings 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2e0a1a39eab95caf4f5556da9289b9e68f0aafac901b2ce80daaf020d3b733a8"
 "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
+"checksum number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dbf9993e59c894e3c08aa1c2712914e9e6bf1fcbfc6bef283e2183df345a4fee"
+"checksum os_pipe 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce1d819d394515aae0530eae02156fedfc98b4f0359bf3048a442be89a95157b"
 "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
 "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
 "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
@@ -1039,6 +1122,7 @@ dependencies = [
 "checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c"
 "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
 "checksum serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0887a8e097a69559b56aa2526bf7aff7c3048cf627dff781f0b56a6001534593"
+"checksum shared_child 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e254d79655b3a1cb86b4079fc080c91664af57d0a81facb59fc828503cd4f48"
 "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be"
 "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
 "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
diff --git a/Cargo.toml b/Cargo.toml
index 2108102..d7ce01f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,8 +7,11 @@ edition = "2018"
 [dependencies]
 console = "0.7.0"
 dialoguer = "0.3.0"
+duct_sh = "0.1.0"
 failure = "0.1.0"
+indicatif = "0.11.0"
 maplit = "1.0.0"
+scopeguard = "1.0.0"
 serde = "1.0.0"
 serde_derive = "1.0.0"
 serde_yaml = "0.8.0"
@@ -19,4 +22,3 @@ assert_cmd = "0.11.0"
 assert_fs = "0.11.0"
 lazy_static = "1.3.0"
 rexpect = "0.3.0"
-scopeguard = "1.0.0"
diff --git a/src/lib.rs b/src/lib.rs
index 15522ac..b8de9c4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,4 @@
+extern crate duct_sh;
 #[macro_use]
 extern crate failure;
 #[macro_use]
@@ -11,20 +12,22 @@ extern crate serde_yaml;
 
 use std::collections::BTreeMap;
 use std::fs::File;
-use std::io::Read;
+use std::io::{self, Read, Write};
 use std::path::{Path, PathBuf};
 
 use console::Style;
 use dialoguer::theme::ColorfulTheme;
 use dialoguer::Confirmation;
+use duct_sh::sh_dangerous;
 use failure::Error;
+use indicatif::{ProgressBar, ProgressStyle};
 
 // Increment the version number every time the version changes. I can't figure out how to
 // break this out into its own const, see https://github.com/rust-lang/rust/issues/52393.
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(tag = "schema_version")]
 enum VersionedCheckListList {
-    #[serde(rename = "1")] // increment here
+    #[serde(rename = "2")] // increment here
     Current(CheckListList),
 }
 
@@ -32,7 +35,12 @@ enum VersionedCheckListList {
 struct CheckListList(BTreeMap<String, CheckList>);
 
 #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
-struct CheckList(Vec<String>);
+struct CheckList {
+    #[serde(default)]
+    automated: Vec<String>,
+    #[serde(default)]
+    manual: Vec<String>,
+}
 
 impl CheckListList {
     fn from_file(path: &Path) -> Result<CheckListList, Error> {
@@ -69,18 +77,55 @@ fn ask_formatted_question(prefix: &str, prompt: &str) -> Result<bool, Error> {
 }
 
 fn question_loop(checklist: &CheckList) -> Result<bool, Error> {
+    let success = Style::new().green();
+    let failure = Style::new().red();
     let mut seen = false;
-    for item in &checklist.0 {
+    for item in &checklist.manual {
         if !seen {
             seen = true;
         } else {
             println!("Great! Continuing...")
         }
         if !ask_formatted_question(&"Have you: ", &item)? {
+            println!("\nmanual tests: {}", failure.apply_to("failed"));
             return Ok(false);
         }
     }
 
+    println!("\nmanual tests: {}", success.apply_to("passed"));
+    Ok(true)
+}
+
+fn shell_loop(checklist: &CheckList) -> Result<bool, Error> {
+    let success = Style::new().green();
+    let failure = Style::new().red();
+    let sty = ProgressStyle::default_bar()
+        .template("{bar:40.green/white} {pos:>2}/{len:7} {wide_msg:.blue}");
+    let b = ProgressBar::new(checklist.automated.len() as u64);
+    b.set_style(sty.clone());
+    let progress_bar = scopeguard::guard(b, |b| {
+        b.finish_and_clear();
+    });
+    for item in &checklist.automated {
+        progress_bar.set_message(&item);
+        let command_res = sh_dangerous(item)
+            .stdout_capture()
+            .stderr_capture()
+            .unchecked()
+            .run()?;
+        if !command_res.status.success() {
+            progress_bar.finish_and_clear();
+            println!("\nautomated tests: {}", failure.apply_to("failed"));
+            println!("{} running: {}\n", failure.apply_to("error"), item);
+            io::stdout().write_all(&command_res.stdout)?;
+            io::stderr().write_all(&command_res.stderr)?;
+            return Ok(false);
+        }
+        progress_bar.inc(1);
+    }
+
+    progress_bar.finish_and_clear();
+    println!("\nautomated tests: {}", success.apply_to("passed"));
     Ok(true)
 }
 
@@ -105,7 +150,7 @@ pub fn run(opts: &Opt) -> Result<(), Error> {
     let failure = Style::new().red();
     let checklists = CheckListList::from_file(&opts.checklist)?;
     if let Some(checklist) = checklists.0.get("committing") {
-        if question_loop(&checklist)? {
+        if shell_loop(&checklist)? && question_loop(&checklist)? {
             println!("{}", success.apply_to("all clear!"));
         } else {
             println!(
@@ -129,12 +174,37 @@ mod tests {
             t.close().unwrap();
         });
         temp.child(".checklist.yml")
-            .write_str("schema_version: 1\ncommitting:\n- test")
+            .write_str(
+                "schema_version: 2\ncommitting:\n  automated: []\n  manual:\n    - test",
+            )
+            .unwrap();
+        assert_eq!(
+            CheckListList::from_file(temp.child(".checklist.yml").path()).unwrap(),
+            CheckListList(btreemap! {
+                String::from("committing") => CheckList{
+                    manual: vec![String::from("test")],
+                    automated: vec![],
+                },
+            }),
+        );
+    }
+
+    #[test]
+    fn test_defaults() {
+        let t = assert_fs::TempDir::new().unwrap();
+        let temp = scopeguard::guard(t, |t| {
+            t.close().unwrap();
+        });
+        temp.child(".checklist.yml")
+            .write_str("schema_version: 2\ncommitting:\n  manual: []")
             .unwrap();
         assert_eq!(
             CheckListList::from_file(temp.child(".checklist.yml").path()).unwrap(),
             CheckListList(btreemap! {
-                String::from("committing") => CheckList(vec![String::from("test")]),
+                String::from("committing") => CheckList{
+                    manual: vec![],
+                    automated: vec![],
+                },
             }),
         );
     }


Test patch:
diff --git a/tests/rexpect_integration_tests.rs b/tests/rexpect_integration_tests.rs
index 8ad4998..c1af499 100644
--- a/tests/rexpect_integration_tests.rs
+++ b/tests/rexpect_integration_tests.rs
@@ -29,7 +29,7 @@ fn with_checklist(tempdir: &TempDir, contents: &str) -> String {
     let checklist_file = tempdir.child(".checklist.yml");
 
     checklist_file
-        .write_str(&format!("schema_version: 1\n{}", contents))
+        .write_str(&format!("schema_version: 2\n{}", contents))
         .unwrap();
 
     checklist_file.path().to_str().unwrap().to_string()
@@ -42,7 +42,7 @@ fn test_basic_usage() {
         t.close().unwrap();
     });
 
-    let checklist_path = with_checklist(&temp, "committing:\n- test");
+    let checklist_path = with_checklist(&temp, "committing:\n  manual:\n    - test");
 
     let mut p = spawn(
         &format!("{} --checklist {}", get_command_path(), checklist_path),
@@ -63,7 +63,7 @@ fn test_basic_rejection() {
         t.close().unwrap();
     });
 
-    let checklist_path = with_checklist(&temp, "committing:\n- test");
+    let checklist_path = with_checklist(&temp, "committing:\n  manual:\n    - test");
 
     let mut p = spawn(
         &format!("{} --checklist {}", get_command_path(), checklist_path),
@@ -84,7 +84,10 @@ fn test_multi_success() {
         t.close().unwrap();
     });
 
-    let checklist_path = with_checklist(&temp, "committing:\n- test\n- test2");
+    let checklist_path = with_checklist(
+        &temp,
+        "committing:\n  automated: []\n  manual:\n    - test\n    - test2",
+    );
 
     let mut p = spawn(
         &format!("{} --checklist {}", get_command_path(), checklist_path),
@@ -107,7 +110,10 @@ fn test_multi_rejection() {
         t.close().unwrap();
     });
 
-    let checklist_path = with_checklist(&temp, "committing:\n- test\n- test2");
+    let checklist_path = with_checklist(
+        &temp,
+        "committing:\n  automated: []\n  manual:\n    - test\n    - test2",
+    );
 
     let mut p = spawn(
         &format!("{} --checklist {}", get_command_path(), checklist_path),