diff --git a/src/main.rs b/src/main.rs index e91ed45..d9df59f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,18 @@ use std::env; use std::path::Path; use clap::{ - crate_authors, crate_description, crate_name, crate_version, App, Arg, ArgMatches, SubCommand, + crate_authors, crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgMatches, + SubCommand, }; use colored::*; use mask::command::Command; +use mask::executor::execute_command; fn main() { let cli_app = App::new(crate_name!()) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::SubcommandRequired) .version(crate_version!()) .author(crate_authors!()) .about(crate_description!()) @@ -24,14 +28,10 @@ fn main() { let root_command = mask::parser::build_command_structure(maskfile.unwrap()); let matches = build_subcommands(cli_app, &root_command.subcommands).get_matches(); - let chosen_cmd = find_command(&matches, &root_command.subcommands); - if chosen_cmd.is_none() { - // Exit with an error - eprintln!("{} missing subcommand", "ERROR:".red()); - std::process::exit(1); - } + let chosen_cmd = find_command(&matches, &root_command.subcommands) + .expect("SubcommandRequired failed to work"); - match mask::executor::execute_command(chosen_cmd.unwrap(), maskfile_path) { + match execute_command(chosen_cmd, maskfile_path) { Ok(status) => match status.code() { Some(code) => std::process::exit(code), None => return, @@ -92,6 +92,10 @@ fn build_subcommands<'a, 'b>( let mut subcmd = SubCommand::with_name(&c.name).about(c.desc.as_ref()); if !c.subcommands.is_empty() { subcmd = build_subcommands(subcmd, &c.subcommands); + // If this parent command has no script source, require a subcommand. + if c.source == "" { + subcmd = subcmd.setting(AppSettings::SubcommandRequired); + } } // Add all required arguments diff --git a/tests/arguments_and_flags_test.rs b/tests/arguments_and_flags_test.rs index eac8799..af10533 100644 --- a/tests/arguments_and_flags_test.rs +++ b/tests/arguments_and_flags_test.rs @@ -1,8 +1,8 @@ use assert_cmd::prelude::*; +use clap::{crate_name, crate_version}; use predicates::str::contains; mod common; - use common::MaskCommandExt; #[test] @@ -84,3 +84,35 @@ fi .stdout(contains("Starting an http server on PORT: 1234")) .success(); } + +mod version_flag { + use super::*; + + #[test] + fn shows_the_correct_version_for_the_root_command() { + let (_temp, maskfile_path) = common::maskfile("## foo"); + + common::run_mask(&maskfile_path) + .command("--version") + .assert() + .stdout(contains(format!("{} {}", crate_name!(), crate_version!()))) + .success(); + } + + #[test] + fn exits_with_error_when_subcommand_has_version_flag() { + let (_temp, maskfile_path) = common::maskfile("## foo"); + + // The setting "VersionlessSubcommands" removes the version flags (-V, --version) + // from subcommands. Only the root command has a version flag. + + common::run_mask(&maskfile_path) + .command("foo") + .arg("--version") + .assert() + .stderr(contains( + "error: Found argument '--version' which wasn't expected, or isn't valid in this context", + )) + .failure(); + } +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs index c7ce4f9..cdd4b9b 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -30,15 +30,15 @@ mod when_no_maskfile_found_in_current_directory { use super::*; #[test] - fn logs_warning_about_missing_maskfile_when_its_not_custom() { + fn logs_warning_about_missing_maskfile() { common::run_mask(&PathBuf::from("./maskfile.md")) .current_dir(".github") + .command("-V") .assert() .stdout(contains(format!( "{} no maskfile.md found", "WARNING:".yellow() - ))) - .success(); + ))); } #[test] diff --git a/tests/subcommands_test.rs b/tests/subcommands_test.rs index 1adfdf7..bce53fb 100644 --- a/tests/subcommands_test.rs +++ b/tests/subcommands_test.rs @@ -1,6 +1,5 @@ use assert_cmd::prelude::*; -use colored::*; -use predicates::str::contains; +use predicates::str::{contains, is_empty}; mod common; @@ -41,7 +40,7 @@ echo "Stopping service $service_name" } #[test] -fn exits_with_error_when_missing_subcommnad() { +fn exits_with_error_when_missing_subcommand() { let (_temp, maskfile_path) = common::maskfile( r#" ## foo @@ -50,6 +49,52 @@ fn exits_with_error_when_missing_subcommnad() { common::run_mask(&maskfile_path) .assert() - .stderr(contains(format!("{} missing subcommand", "ERROR:".red()))) + .stderr(contains( + "error: 'mask' requires a subcommand, but one was not provided", + )) .failure(); } + +mod when_command_has_no_source { + use super::*; + + #[test] + fn exits_gracefully_when_it_has_no_subcommands() { + let (_temp, maskfile_path) = common::maskfile( + r#" +## system +"#, + ); + + // NOTE: Right now we exit without an error. Perhaps there should at least + // be a warning logged to the console? + common::run_mask(&maskfile_path) + .command("system") + .assert() + .stdout(is_empty()) + .success(); + } + + #[test] + fn exits_with_error_when_it_has_subcommands() { + let (_temp, maskfile_path) = common::maskfile( + r#" +## system + +### start + +~~~sh +echo "system, online" +~~~ +"#, + ); + + common::run_mask(&maskfile_path) + .command("system") + .assert() + .stderr(contains( + "error: 'mask system' requires a subcommand, but one was not provided", + )) + .failure(); + } +}