Repository: swellaby/rusty-hook (rust)

Issue:
Enhance multi-command documentation
When array-syntax support was added for specifying multiple commands the documentation was updated to describe the new behavior. However, this could potentially be misleading and give the impression that's the _only_ way to run multiple commands (as opposed to using `&&`.

We should enhance the docs to be more explicit about specifying the different options, and clarify that the array syntax is only supported in v0.12+

Pull request description:
Better explanation for multiple commands in the Hooks section of the README
## Changes
<!-- Summary of changes included in this PR -->
- [x] Added a better explanation for multiple commands in hooks
- [x] Minor formatting of the markdown file (Removal of trailing whitespaces)

## Related Issues
<!-- List any related/impacted issues -->
- Refs #137 


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 551bee3..7ca671d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -25,9 +25,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
 
 [[package]]
 name = "ci_info"
-version = "0.13.2"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d909294977dd20c841a88849a9d56589bdb2a9ce887bc020b2f575a83d6c480"
+checksum = "61eed4218981025780de67d238868a4e3f4e18ac84ace81041c4ccc74eb94c49"
 dependencies = [
  "envmnt",
 ]
@@ -66,9 +66,9 @@ dependencies = [
 
 [[package]]
 name = "envmnt"
-version = "0.8.4"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2d328fc287c61314c4a61af7cfdcbd7e678e39778488c7cb13ec133ce0f4059"
+checksum = "dbfac51e9996e41d78a943227b7f313efcebf545b21584a0e213b956a062e11e"
 dependencies = [
  "fsio",
  "indexmap",
@@ -76,9 +76,9 @@ dependencies = [
 
 [[package]]
 name = "fsio"
-version = "0.1.3"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3"
+checksum = "a50045aa8931ae01afbc5d72439e8f57f326becb8c70d07dfc816778eff3d167"
 
 [[package]]
 name = "hashbrown"
diff --git a/Cargo.toml b/Cargo.toml
index 7f459e0..8736d1c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,13 +31,13 @@ exclude = [
 ]
 
 [dependencies]
-ci_info = "0.13.2"
+ci_info = "0.14.1"
 clap = "3.0.0-beta.2"
 toml = "0.5.8"
 nias = "0.7.0"
 
 [build-dependencies]
-ci_info = "0.13.2"
+ci_info = "0.14.1"
 toml = "0.5.8"
 nias = "0.7.0"
 
diff --git a/LICENSE b/LICENSE
index 9e4790f..a705a20 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2018-2020 Swellaby
+Copyright (c) 2018-2021 Swellaby
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index c2a5ed4..38e78b1 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,20 @@
 # rusty-hook
-Git hook utility for Rust codebases that lets you run any script for any git hook. 
+Git hook utility for Rust codebases that lets you run any script for any git hook.
 
-Functional, but still in Beta! 
+Functional, but still in Beta!
 
 [![Version Badge][version-badge]][crate url]
 [![Downloads Badge][downloads-badge]][crate url]
-[![License Badge][license-badge]][crate url]  
+[![License Badge][license-badge]][crate url]
 
 [![Linux CI Badge][linux-ci-badge]][linux-ci-url]
 [![Mac CI Badge][mac-ci-badge]][mac-ci-url]
-[![Windows CI Badge][windows-ci-badge]][windows-ci-url]  
+[![Windows CI Badge][windows-ci-badge]][windows-ci-url]
 [![Test Results Badge][tests-badge]][tests-url]
 [![Coverage Badge][coverage-badge]][coverage-url]
 
 ## Quick Start
-Pre-requisites: Make sure you have Rust installed and that Cargo's bin directory is on your PATH. 
+Pre-requisites: Make sure you have Rust installed and that Cargo's bin directory is on your PATH.
 https://www.rust-lang.org/tools/install
 
 1. Add `rusty-hook` as a dev dependency in your Cargo.toml file
@@ -70,10 +70,10 @@ post-commit = "echo yay"
 verbose = true
 ```
 ### Hooks
-Under the `[hooks]` table, you can add an entry for any and every git hook you want to run by adding a key using the name of the [git hook][git hooks], and then specify the command/script you want to run for that hook. Multiple commands in a form of a toml array are also allowed. Whenever that git hook is triggered, `rusty-hook` will run your specified command!
+Under the `[hooks]` table, you can add an entry for any and every git hook you want to run by adding a key using the name of the [git hook][git hooks], and then specify the command/script you want to run for that hook. Multiple commands in a form of a toml array or via command chaining using `&&` are also allowed (Only for versions 0.12 and up). Whenever that git hook is triggered, `rusty-hook` will run your specified command!
 
 #### Using git arguments
-In git hook commands, any instance of `%rh!` will be replaced by the arguments that git passes to this hook.  
+In git hook commands, any instance of `%rh!` will be replaced by the arguments that git passes to this hook.
 
 ```toml
 [hooks]
@@ -102,7 +102,7 @@ All contributions are welcome and appreciated! Check out our [Contributing Guide
 [See CHANGELOG.md](https://github.com/swellaby/rusty-hook/blob/master/CHANGELOG.md)
 
 ## Removing rusty-hook
-We'll be sad to see you go, but here's what to do if you'd like to remove `rusty-hook` from your project. 
+We'll be sad to see you go, but here's what to do if you'd like to remove `rusty-hook` from your project.
 
 1. Remove the `rusty-hook` dev dependency from the `Cargo.toml` file in your project.
 2. Remove the `.rusty-hook.toml` configuration file from your project.
diff --git a/build.rs b/build.rs
index ac11a7b..c40b15d 100644
--- a/build.rs
+++ b/build.rs
@@ -2,8 +2,8 @@
 #[path = "src/rusty_hook.rs"]
 mod rusty_hook;
 
-use std::env;
 use std::process::exit;
+use std::{env, vec};
 
 fn main() {
     if ci_info::is_ci() {
@@ -16,6 +16,7 @@ fn main() {
         nias::get_file_writer(),
         nias::get_file_existence_checker(),
         Some(&target_directory),
+        vec![],
     ) {
         println!(
             "Fatal error encountered during initialization. Details: {}",
diff --git a/src/git.rs b/src/git.rs
index 73441dd..0b2559d 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -47,6 +47,7 @@ pub fn setup_hooks<F, G>(
     run_command: F,
     write_file: G,
     root_directory_path: &str,
+    hook_file_skip_list: &Vec<&str>,
 ) -> Result<(), String>
 where
     F: Fn(
@@ -61,7 +62,12 @@ where
         Ok(Some(path)) => path,
         _ => return Err(String::from("Failure determining git hooks directory")),
     };
-    hooks::create_hook_files(write_file, &root_directory_path, &hooks_directory)
+    hooks::create_hook_files(
+        write_file,
+        &root_directory_path,
+        &hooks_directory,
+        hook_file_skip_list,
+    )
 }
 
 #[cfg(test)]
diff --git a/src/hooks.rs b/src/hooks.rs
index 79a757e..1089194 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -67,12 +67,16 @@ pub fn create_hook_files<F>(
     write_file: F,
     root_directory_path: &str,
     hooks_directory: &str,
+    hook_file_skip_list: &Vec<&str>,
 ) -> Result<(), String>
 where
     F: Fn(&str, &str, bool) -> Result<(), String>,
 {
     let hook_file_contents = get_hook_file_contents();
-    for hook in HOOK_NAMES.iter() {
+    for hook in HOOK_NAMES
+        .iter()
+        .filter(|h| !hook_file_skip_list.contains(h))
+    {
         let path = get_file_path(root_directory_path, hooks_directory, hook);
         if write_file(&path, &hook_file_contents, true).is_err() {
             return Err(String::from(HOOK_CREATION_ERROR));
diff --git a/src/main.rs b/src/main.rs
index d0878ed..d0788de 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,7 +10,10 @@ mod rusty_hook;
 enum RustyHookOpts {
     /// Initialize rusty-hook's git hooks in the current directory.
     #[clap(author, version)]
-    Init,
+    Init {
+        #[clap(long)]
+        skip_hook_list: Option<String>,
+    },
     /// Print the current version of rusty-hook.
     #[clap(author, version, alias = "-v")]
     Version,
@@ -25,16 +28,21 @@ enum RustyHookOpts {
     },
 }
 
-fn init() {
+fn init(skip_hook_list: Option<String>) {
     if ci_info::is_ci() {
         println!("[rusty-hook] CI Environment detected. Skipping hook install");
         exit(0);
     }
 
+    let skip_hook_list = skip_hook_list
+        .as_deref()
+        .map_or(vec![], |s| s.split(",").collect());
+
     if let Err(err) = rusty_hook::init(
         nias::get_command_runner(),
         nias::get_file_writer(),
         nias::get_file_existence_checker(),
+        skip_hook_list,
     ) {
         eprintln!(
             "[rusty-hook] Fatal error encountered during initialization. Details: {}",
@@ -69,7 +77,7 @@ fn run(hook: String, args: Option<String>) {
 fn main() {
     let opts = RustyHookOpts::parse();
     match opts {
-        RustyHookOpts::Init => init(),
+        RustyHookOpts::Init { skip_hook_list } => init(skip_hook_list),
         RustyHookOpts::Version => println!(env!("CARGO_PKG_VERSION")),
         RustyHookOpts::Run { hook, args } => run(hook, args),
     }
diff --git a/src/rusty_hook.rs b/src/rusty_hook.rs
index 4365f9d..893b5f7 100644
--- a/src/rusty_hook.rs
+++ b/src/rusty_hook.rs
@@ -13,6 +13,7 @@ pub fn init_directory<F, G, H>(
     write_file: G,
     file_exists: H,
     target_directory: Option<&str>,
+    hook_file_skip_list: Vec<&str>,
 ) -> Result<(), String>
 where
     F: Fn(
@@ -28,8 +29,14 @@ where
         Ok(Some(path)) => path,
         _ => return Err(String::from("Failure determining git repo root directory")),
     };
-
-    if git::setup_hooks(&run_command, &write_file, &root_directory_path).is_err() {
+    if git::setup_hooks(
+        &run_command,
+        &write_file,
+        &root_directory_path,
+        &hook_file_skip_list,
+    )
+    .is_err()
+    {
         return Err(String::from("Unable to create git hooks"));
     };
 
@@ -41,7 +48,12 @@ where
     Ok(())
 }
 
-pub fn init<F, G, H>(run_command: F, write_file: G, file_exists: H) -> Result<(), String>
+pub fn init<F, G, H>(
+    run_command: F,
+    write_file: G,
+    file_exists: H,
+    hook_file_skip_list: Vec<&str>,
+) -> Result<(), String>
 where
     F: Fn(
         &str,
@@ -52,7 +64,13 @@ where
     G: Fn(&str, &str, bool) -> Result<(), String>,
     H: Fn(&str) -> Result<bool, ()>,
 {
-    init_directory(&run_command, &write_file, &file_exists, None)
+    init_directory(
+        &run_command,
+        &write_file,
+        &file_exists,
+        None,
+        hook_file_skip_list,
+    )
 }
 
 pub fn run<F, G, H, I>(


Test patch:
diff --git a/src/git_test.rs b/src/git_test.rs
index 49f7ec9..dd8b950 100644
--- a/src/git_test.rs
+++ b/src/git_test.rs
@@ -73,7 +73,7 @@ mod setup_hooks_tests {
         let exp_err = "Failure determining git hooks directory";
         let run_command = build_simple_command_runner(Err(None));
         let write_file = |_path: &str, _contents: &str, _x: bool| Ok(());
-        let result = setup_hooks(run_command, write_file, "");
+        let result = setup_hooks(run_command, write_file, "", &vec![]);
         assert_eq!(result, Err(String::from(exp_err)));
     }
 
@@ -82,7 +82,7 @@ mod setup_hooks_tests {
         let run_command =
             build_simple_command_runner(Ok(Some(String::from("/usr/repos/foo/.git/hooks"))));
         let write_file = |_path: &str, _contents: &str, _x: bool| Err(String::from(""));
-        let result = setup_hooks(run_command, write_file, "");
+        let result = setup_hooks(run_command, write_file, "", &vec![]);
         assert_eq!(result, Err(String::from(hooks::HOOK_CREATION_ERROR)));
     }
 
@@ -92,7 +92,7 @@ mod setup_hooks_tests {
         let git_hooks = ".git/hooks";
         let run_command = build_simple_command_runner(Ok(Some(String::from(git_hooks))));
         let write_file = |_p: &str, _c: &str, _x: bool| Ok(());
-        let result = setup_hooks(run_command, write_file, root_dir);
+        let result = setup_hooks(run_command, write_file, root_dir, &vec![]);
         assert_eq!(result, Ok(()));
     }
 }
diff --git a/src/hooks_test.rs b/src/hooks_test.rs
index 656f697..4050c9b 100644
--- a/src/hooks_test.rs
+++ b/src/hooks_test.rs
@@ -28,6 +28,7 @@ const EXP_HOOK_NAMES: [&str; 19] = [
     "post-rewrite",
     "sendemail-validate",
 ];
+const EXP_SKIPPED_HOOK: &str = "commit-msg";
 
 const EXP_CLI_SCRIPT_NAME: &str = "cli.sh";
 const EXP_SEMVER_SCRIPT_NAME: &str = "semver.sh";
@@ -123,7 +124,7 @@ mod create_hook_files_tests {
                 _ => Err(String::from("")),
             }
         };
-        let result = create_hook_files(write_file, "", "");
+        let result = create_hook_files(write_file, "", "", &vec![]);
         assert_eq!(result, Err(String::from(EXP_HOOK_CREATION_ERROR)));
     }
 
@@ -137,7 +138,7 @@ mod create_hook_files_tests {
                 _ => Ok(()),
             }
         };
-        let result = create_hook_files(write_file, "", "");
+        let result = create_hook_files(write_file, "", "", &vec![]);
         assert_eq!(result, Err(String::from(EXP_HOOK_CREATION_ERROR)));
     }
 
@@ -151,7 +152,7 @@ mod create_hook_files_tests {
                 _ => Ok(()),
             }
         };
-        let result = create_hook_files(write_file, "", "");
+        let result = create_hook_files(write_file, "", "", &vec![]);
         assert_eq!(result, Err(String::from(EXP_HOOK_CREATION_ERROR)));
     }
 
@@ -185,7 +186,42 @@ mod create_hook_files_tests {
             assert_eq!(true, make_executable);
             Ok(())
         };
-        let result = create_hook_files(write_file, root_dir, git_hooks);
+        let result = create_hook_files(write_file, root_dir, git_hooks, &vec![]);
+        assert_eq!(result, Ok(()));
+    }
+
+    #[test]
+    fn does_not_create_skipped_hook() {
+        let root_dir = "/usr/repos/foo";
+        let git_hooks = ".git/hooks";
+        let exp_contents = get_expected_hook_file_contents();
+        let exp_cli_contents = get_expected_cli_script_file_contents();
+        let exp_cli_path = &format!("{}/{}/{}", root_dir, git_hooks, EXP_CLI_SCRIPT_NAME);
+        let exp_semver_path = &format!("{}/{}/{}", root_dir, git_hooks, EXP_SEMVER_SCRIPT_NAME);
+        let exp_semver_contents = get_expected_semver_script_file_contents();
+        let write_file = |path: &str, contents: &str, make_executable: bool| {
+            let file_name = &&path[(path.rfind('/').unwrap() + 1)..];
+            match *file_name {
+                EXP_CLI_SCRIPT_NAME => {
+                    assert_eq!(exp_cli_path, path);
+                    assert_eq!(exp_cli_contents, contents);
+                }
+                EXP_SEMVER_SCRIPT_NAME => {
+                    assert_eq!(exp_semver_path, path);
+                    assert_eq!(exp_semver_contents, contents);
+                }
+                EXP_SKIPPED_HOOK => panic!("Should not create skipped hook"),
+                _ => {
+                    let exp_hook = EXP_HOOK_NAMES.iter().find(|&n| n == file_name).unwrap();
+                    let exp_path = &format!("{}/{}/{}", root_dir, git_hooks, exp_hook);
+                    assert_eq!(exp_path, path);
+                    assert_eq!(exp_contents, contents);
+                }
+            }
+            assert_eq!(true, make_executable);
+            Ok(())
+        };
+        let result = create_hook_files(write_file, root_dir, git_hooks, &vec![EXP_SKIPPED_HOOK]);
         assert_eq!(result, Ok(()));
     }
 }
diff --git a/src/rusty_hook_test.rs b/src/rusty_hook_test.rs
index 7834401..6b21cc6 100644
--- a/src/rusty_hook_test.rs
+++ b/src/rusty_hook_test.rs
@@ -39,7 +39,7 @@ mod init_directory_tests {
             panic!("Should not get here");
         };
         let file_exists = |_path: &str| panic!("Should not get here");
-        let result = init(run_command, write_file, file_exists);
+        let result = init(run_command, write_file, file_exists, vec![]);
         assert_eq!(result, Err(String::from(exp_err)));
     }
 
@@ -48,7 +48,7 @@ mod init_directory_tests {
         let run_command = build_simple_command_runner(Ok(Some(String::from(""))));
         let write_file = |_file_path: &str, _contents: &str, _x: bool| Err(String::from(""));
         let file_exists = |_path: &str| panic!("Should not get here");
-        let result = init(run_command, write_file, file_exists);
+        let result = init(run_command, write_file, file_exists, vec![]);
         assert_eq!(result, Err(String::from("Unable to create git hooks")));
     }
 
@@ -57,7 +57,7 @@ mod init_directory_tests {
         let run_command = build_simple_command_runner(Ok(Some(String::from(""))));
         let write_file = |_file_path: &str, _contents: &str, _x: bool| Ok(());
         let file_exists = |_path: &str| Err(());
-        let result = init(run_command, write_file, file_exists);
+        let result = init(run_command, write_file, file_exists, vec![]);
         assert_eq!(result, Err(String::from("Unable to create config file")));
     }
 
@@ -66,7 +66,7 @@ mod init_directory_tests {
         let run_command = build_simple_command_runner(Ok(Some(String::from(""))));
         let write_file = |_file_path: &str, _contents: &str, _x: bool| Ok(());
         let file_exists = |_path: &str| Ok(false);
-        let result = init(run_command, write_file, file_exists);
+        let result = init(run_command, write_file, file_exists, vec![]);
         assert_eq!(result, Ok(()));
     }
 }
@@ -91,7 +91,7 @@ mod init_tests {
         };
         let write_file = |_file_path: &str, _contents: &str, _x: bool| Ok(());
         let file_exists = |_path: &str| Ok(false);
-        let result = init(run_command, write_file, file_exists);
+        let result = init(run_command, write_file, file_exists, vec![]);
         assert_eq!(result, Ok(()));
     }
 }