From 51dae216a09a32b6006398deda4ebd8eed36a6e9 Mon Sep 17 00:00:00 2001 From: Jake Deichert Date: Wed, 2 Oct 2019 00:17:49 -0400 Subject: [PATCH 1/8] Add a Script type --- src/command.rs | 27 ++++++++++++++++++++------- src/executor.rs | 16 ++++++++-------- src/main.rs | 2 +- src/parser.rs | 8 ++++---- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/command.rs b/src/command.rs index 096bacf..66df2ee 100644 --- a/src/command.rs +++ b/src/command.rs @@ -3,10 +3,7 @@ pub struct Command { pub cmd_level: u8, pub name: String, pub desc: String, - // The executor to run the source with - pub executor: String, // shell, node, ruby, python, etc... - // The script source to execute - pub source: String, + pub script: Script, pub subcommands: Vec, pub required_args: Vec, pub option_flags: Vec, @@ -18,8 +15,7 @@ impl Command { cmd_level, name: "".to_string(), desc: "".to_string(), - executor: "".to_string(), - source: "".to_string(), + script: Script::new(), subcommands: vec![], required_args: vec![], option_flags: vec![], @@ -28,7 +24,7 @@ impl Command { pub fn build(mut self) -> Self { // Auto add common flags like verbose for commands that have a script source - if !self.source.is_empty() { + if !self.script.source.is_empty() { self.option_flags.push(OptionFlag { name: "verbose".to_string(), desc: "Sets the level of verbosity".to_string(), @@ -44,6 +40,23 @@ impl Command { } } +#[derive(Debug, Clone)] +pub struct Script { + // The executor to run the source with + pub executor: String, // shell, node, ruby, python, etc... + // The script source to execute + pub source: String, +} + +impl Script { + pub fn new() -> Self { + Self { + executor: "".to_string(), + source: "".to_string(), + } + } +} + #[derive(Debug, Clone)] pub struct RequiredArg { pub name: String, diff --git a/src/executor.rs b/src/executor.rs index 6e945c6..d5a2507 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -9,35 +9,35 @@ use clap::crate_name; use crate::command::Command; pub fn execute_command(cmd: Command, maskfile_path: String) -> Result { - let mut child = match cmd.executor.as_ref() { + let mut child = match cmd.script.executor.as_ref() { "js" | "javascript" => { let mut child = process::Command::new("node"); - child.arg("-e").arg(cmd.source); + child.arg("-e").arg(cmd.script.source); child } "py" | "python" => { let mut child = process::Command::new("python"); - child.arg("-c").arg(cmd.source); + child.arg("-c").arg(cmd.script.source); child } "rb" | "ruby" => { let mut child = process::Command::new("ruby"); - child.arg("-e").arg(cmd.source); + child.arg("-e").arg(cmd.script.source); child } "php" => { let mut child = process::Command::new("php"); - child.arg("-r").arg(cmd.source); + child.arg("-r").arg(cmd.script.source); child } "bash" | "zsh" | "fish" => { - let mut child = process::Command::new(cmd.executor); - child.arg("-c").arg(cmd.source); + let mut child = process::Command::new(cmd.script.executor); + child.arg("-c").arg(cmd.script.source); child } _ => { let mut child = process::Command::new("sh"); - child.arg("-c").arg(cmd.source); + child.arg("-c").arg(cmd.script.source); child } }; diff --git a/src/main.rs b/src/main.rs index 880d481..847f46f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,7 +93,7 @@ fn build_subcommands<'a, 'b>( 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 == "" { + if c.script.source == "" { subcmd = subcmd.setting(AppSettings::SubcommandRequired); } } diff --git a/src/parser.rs b/src/parser.rs index 1f767c4..a6d1b21 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -26,7 +26,7 @@ pub fn build_command_structure(maskfile_contents: String) -> Command { current_command = Command::new(heading_level as u8); } Tag::CodeBlock(lang_code) => { - current_command.executor = lang_code.to_string(); + current_command.script.executor = lang_code.to_string(); } Tag::List(_) => { // We're in an options list if the current text above it is "OPTIONS" @@ -51,7 +51,7 @@ pub fn build_command_structure(maskfile_contents: String) -> Command { current_command.desc = text.clone(); } Tag::CodeBlock(_) => { - current_command.source = text.to_string(); + current_command.script.source = text.to_string(); } Tag::List(_) => { // Don't go lower than zero (for cases where it's a non-OPTIONS list) @@ -70,11 +70,11 @@ pub fn build_command_structure(maskfile_contents: String) -> Command { }, Text(body) => { text += &body.to_string(); - // Level 1 is the flag name + // Options level 1 is the flag name if list_level == 1 { current_option_flag.name = text.clone(); } - // Level 2 is the flag config + // Options level 2 is the flag config else if list_level == 2 { let mut config_split = text.splitn(2, ":"); let param = config_split.next().unwrap_or("").trim(); From eec34bc9115eea54c3d413f987d3b640c430a425 Mon Sep 17 00:00:00 2001 From: Jake Deichert Date: Sun, 6 Oct 2019 14:26:28 -0400 Subject: [PATCH 2/8] Parse for an ON::INIT script and run it in the subshell before executing the command --- src/command.rs | 4 ++ src/executor.rs | 116 ++++++++++++++++++++++++++++++++++++++---------- src/main.rs | 4 +- src/parser.rs | 79 ++++++++++++++++++++++++++------- 4 files changed, 161 insertions(+), 42 deletions(-) diff --git a/src/command.rs b/src/command.rs index 66df2ee..336b95c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -55,6 +55,10 @@ impl Script { source: "".to_string(), } } + + pub fn has_script(&self) -> bool { + self.source != "" + } } #[derive(Debug, Clone)] diff --git a/src/executor.rs b/src/executor.rs index d5a2507..336cbd7 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,62 +1,116 @@ use std::fs::canonicalize; use std::io::Result; +use std::io::{Error, ErrorKind}; use std::path::{Path, PathBuf}; use std::process; use std::process::ExitStatus; use clap::crate_name; -use crate::command::Command; +use crate::command::{Command, Script}; -pub fn execute_command(cmd: Command, maskfile_path: String) -> Result { - let mut child = match cmd.script.executor.as_ref() { +pub fn execute_command( + init_script: Script, + cmd: Command, + maskfile_path: String, +) -> Result { + let mut child; + if init_script.has_script() { + if !validate_init_script(&init_script) { + let msg = "ON::INIT must be a shell-based script executor."; + return Err(Error::new(ErrorKind::Other, msg)); + } + child = prepare_command_with_init_script(init_script, &cmd); + } else { + child = prepare_command_without_init_script(&cmd); + } + + child = add_utility_variables(child, maskfile_path); + child = add_flag_variables(child, &cmd); + + child.spawn()?.wait() +} + +fn prepare_command_without_init_script(cmd: &Command) -> process::Command { + let executor = cmd.script.executor.clone(); + let source = cmd.script.source.clone(); + + match executor.as_ref() { "js" | "javascript" => { - let mut child = process::Command::new("node"); - child.arg("-e").arg(cmd.script.source); + let mut child; + child = process::Command::new("node"); + child.arg("-e").arg(source); child } "py" | "python" => { let mut child = process::Command::new("python"); - child.arg("-c").arg(cmd.script.source); + child.arg("-c").arg(source); child } "rb" | "ruby" => { let mut child = process::Command::new("ruby"); - child.arg("-e").arg(cmd.script.source); + child.arg("-e").arg(source); child } "php" => { let mut child = process::Command::new("php"); - child.arg("-r").arg(cmd.script.source); + child.arg("-r").arg(source); child } "bash" | "zsh" | "fish" => { - let mut child = process::Command::new(cmd.script.executor); - child.arg("-c").arg(cmd.script.source); + let mut child = process::Command::new(executor); + child.arg("-c").arg(source); child } _ => { let mut child = process::Command::new("sh"); - child.arg("-c").arg(cmd.script.source); + child.arg("-c").arg(source); child } - }; - - child = add_utility_variables(child, maskfile_path); - - // Add all required args as environment variables - for arg in cmd.required_args { - child.env(arg.name, arg.val); } +} - // Add all optional flags as environment variables if they have a value - for flag in cmd.option_flags { - if flag.val != "" { - child.env(flag.name, flag.val); +fn prepare_command_with_init_script(init_script: Script, cmd: &Command) -> process::Command { + let executor = cmd.script.executor.clone(); + + match executor.as_ref() { + "js" | "javascript" => run_with_init_script(&init_script, &cmd, "node -e"), + "py" | "python" => run_with_init_script(&init_script, &cmd, "python -c"), + "rb" | "ruby" => run_with_init_script(&init_script, &cmd, "ruby -e"), + "php" => run_with_init_script(&init_script, &cmd, "php -r"), + "bash" | "zsh" | "fish" => { + run_with_init_script(&init_script, &cmd, &format!("{} -c", executor)) } + _ => run_with_init_script(&init_script, &cmd, "sh -c"), } +} - child.spawn()?.wait() +fn run_with_init_script( + init_script: &Script, + cmd: &Command, + executor_invocation: &str, +) -> process::Command { + let mut child = process::Command::new(init_script.executor.clone()); + // Combine the init script with the command to run + let source = format!( + "{}\n{} \"{}\"", + init_script.source.clone(), + executor_invocation, + "$MASK_CMD_SOURCE" + ); + child + .env("MASK_CMD_SOURCE", cmd.script.source.clone()) + .arg("-c") + .arg(source); + child +} + +// Validate the subshell init script is shell-based +fn validate_init_script(init_script: &Script) -> bool { + match init_script.executor.as_ref() { + "js" | "javascript" | "py" | "python" | "rb" | "ruby" | "php" => false, + _ => true, + } } // Add some useful environment variables that scripts can use @@ -88,3 +142,19 @@ fn add_utility_variables(mut child: process::Command, maskfile_path: String) -> child } + +fn add_flag_variables(mut child: process::Command, cmd: &Command) -> process::Command { + // Add all required args as environment variables + for arg in &cmd.required_args { + child.env(arg.name.clone(), arg.val.clone()); + } + + // Add all optional flags as environment variables if they have a value + for flag in &cmd.option_flags { + if flag.val != "" { + child.env(flag.name.clone(), flag.val.clone()); + } + } + + child +} diff --git a/src/main.rs b/src/main.rs index 847f46f..443bd29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,12 +23,12 @@ fn main() { return; } - let root_command = mask::parser::build_command_structure(maskfile.unwrap()); + let (root_command, init_script) = 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) .expect("SubcommandRequired failed to work"); - match execute_command(chosen_cmd, maskfile_path) { + match execute_command(init_script, chosen_cmd, maskfile_path) { Ok(status) => match status.code() { Some(code) => std::process::exit(code), None => return, diff --git a/src/parser.rs b/src/parser.rs index a6d1b21..b96a1ab 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,15 +3,19 @@ use pulldown_cmark::{ Options, Parser, Tag, }; -use crate::command::{Command, OptionFlag, RequiredArg}; +use crate::command::{Command, OptionFlag, RequiredArg, Script}; -pub fn build_command_structure(maskfile_contents: String) -> Command { +// Woof. This is ugly. I'm planning on giving this a rewrite at some point... +// At least we have some decent tests in place. +pub fn build_command_structure(maskfile_contents: String) -> (Command, Script) { let parser = create_markdown_parser(&maskfile_contents); let mut commands = vec![]; let mut current_command = Command::new(1); let mut current_option_flag = OptionFlag::new(); let mut text = "".to_string(); let mut list_level = 0; + let mut init_script = Script::new(); + let mut init_script_state = ParserPatternState::new(); for event in parser { match event { @@ -26,7 +30,11 @@ pub fn build_command_structure(maskfile_contents: String) -> Command { current_command = Command::new(heading_level as u8); } Tag::CodeBlock(lang_code) => { - current_command.script.executor = lang_code.to_string(); + if init_script_state.is_open() { + init_script.executor = lang_code.to_string(); + } else { + current_command.script.executor = lang_code.to_string(); + } } Tag::List(_) => { // We're in an options list if the current text above it is "OPTIONS" @@ -51,7 +59,12 @@ pub fn build_command_structure(maskfile_contents: String) -> Command { current_command.desc = text.clone(); } Tag::CodeBlock(_) => { - current_command.script.source = text.to_string(); + if init_script_state.is_open() { + init_script.source = text.to_string(); + init_script_state.closed = true; + } else { + current_command.script.source = text.to_string(); + } } Tag::List(_) => { // Don't go lower than zero (for cases where it's a non-OPTIONS list) @@ -70,8 +83,13 @@ pub fn build_command_structure(maskfile_contents: String) -> Command { }, Text(body) => { text += &body.to_string(); + + // Look for a shell initialization script + if list_level == 0 && text == "ON::INIT" && !init_script_state.found { + init_script_state.found = true; + } // Options level 1 is the flag name - if list_level == 1 { + else if list_level == 1 { current_option_flag.name = text.clone(); } // Options level 2 is the flag config @@ -127,7 +145,31 @@ pub fn build_command_structure(maskfile_contents: String) -> Command { // Convert the flat commands array and to a tree of subcommands based on level let all = treeify_commands(commands); let root_command = all.first().expect("root command must exist"); - root_command.clone() + + // The command root and a possible init script + (root_command.clone(), init_script) +} + +#[derive(Debug, Clone)] +pub struct ParserPatternState { + // Whether this pattern has been found + pub found: bool, + // Whether this pattern has been closed + pub closed: bool, +} + +impl ParserPatternState { + pub fn new() -> Self { + Self { + found: false, + closed: false, + } + } + + // Open means we're currently parsing this pattern and haven't closed it yet + pub fn is_open(&self) -> bool { + self.found && !self.closed + } } fn create_markdown_parser<'a>(maskfile_contents: &'a String) -> Parser<'a> { @@ -257,7 +299,7 @@ mod build_command_structure { #[test] fn parses_serve_command_name() { - let tree = build_command_structure(TEST_MASKFILE.to_string()); + let (tree, _) = build_command_structure(TEST_MASKFILE.to_string()); let serve_command = &tree .subcommands .iter() @@ -268,7 +310,7 @@ mod build_command_structure { #[test] fn parses_serve_command_description() { - let tree = build_command_structure(TEST_MASKFILE.to_string()); + let (tree, _) = build_command_structure(TEST_MASKFILE.to_string()); let serve_command = &tree .subcommands .iter() @@ -279,7 +321,7 @@ mod build_command_structure { #[test] fn parses_serve_required_positional_arguments() { - let tree = build_command_structure(TEST_MASKFILE.to_string()); + let (tree, _) = build_command_structure(TEST_MASKFILE.to_string()); let serve_command = &tree .subcommands .iter() @@ -291,43 +333,46 @@ mod build_command_structure { #[test] fn parses_serve_command_executor() { - let tree = build_command_structure(TEST_MASKFILE.to_string()); + let (tree, _) = build_command_structure(TEST_MASKFILE.to_string()); let serve_command = &tree .subcommands .iter() .find(|cmd| cmd.name == "serve") .expect("serve command missing"); - assert_eq!(serve_command.executor, "bash"); + assert_eq!(serve_command.script.executor, "bash"); } #[test] fn parses_serve_command_source_with_tildes() { - let tree = build_command_structure(TEST_MASKFILE.to_string()); + let (tree, _) = build_command_structure(TEST_MASKFILE.to_string()); let serve_command = &tree .subcommands .iter() .find(|cmd| cmd.name == "serve") .expect("serve command missing"); - assert_eq!(serve_command.source, "echo \"Serving on port $port\"\n"); + assert_eq!( + serve_command.script.source, + "echo \"Serving on port $port\"\n" + ); } #[test] fn parses_node_command_source_with_backticks() { - let tree = build_command_structure(TEST_MASKFILE.to_string()); + let (tree, _) = build_command_structure(TEST_MASKFILE.to_string()); let node_command = &tree .subcommands .iter() .find(|cmd| cmd.name == "node") .expect("node command missing"); assert_eq!( - node_command.source, + node_command.script.source, "const { name } = process.env;\nconsole.log(`Hello, ${name}!`);\n" ); } #[test] fn adds_verbose_optional_flag_to_command_with_script() { - let tree = build_command_structure(TEST_MASKFILE.to_string()); + let (tree, _) = build_command_structure(TEST_MASKFILE.to_string()); let node_command = tree .subcommands .iter() @@ -348,7 +393,7 @@ mod build_command_structure { #[test] fn does_not_add_verbose_optional_flag_to_command_with_no_script() { - let tree = build_command_structure(TEST_MASKFILE.to_string()); + let (tree, _) = build_command_structure(TEST_MASKFILE.to_string()); let no_script_command = tree .subcommands .iter() From e09913fbbe7237061b534b8eb620e8f39faac626 Mon Sep 17 00:00:00 2001 From: Jake Deichert Date: Sun, 6 Oct 2019 14:55:59 -0400 Subject: [PATCH 3/8] Exit with error code 1 when execute_command returns a custom error --- src/main.rs | 5 ++++- tests/integration_test.rs | 4 ++++ tests/subcommands_test.rs | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 443bd29..33127cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,10 @@ fn main() { Some(code) => std::process::exit(code), None => return, }, - Err(err) => eprintln!("{} {}", "ERROR:".red(), err), + Err(err) => { + eprintln!("{} {}", "ERROR:".red(), err); + std::process::exit(1) + } } } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index cdd4b9b..148b93a 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -67,6 +67,7 @@ mod when_no_maskfile_found_in_current_directory { .current_dir(".github") .command("nothing") .assert() + .code(1) .stderr(contains("error: Found argument 'nothing' which wasn't expected, or isn't valid in this context")) .failure(); } @@ -80,6 +81,7 @@ mod when_custom_specified_maskfile_not_found { common::run_mask(&PathBuf::from("./nonexistent.md")) .command("--help") .assert() + .code(1) .stderr(contains(format!( "{} specified maskfile not found", "ERROR:".red() @@ -92,6 +94,7 @@ mod when_custom_specified_maskfile_not_found { common::run_mask(&PathBuf::from("./nonexistent.md")) .command("--version") .assert() + .code(1) .stderr(contains(format!( "{} specified maskfile not found", "ERROR:".red() @@ -104,6 +107,7 @@ mod when_custom_specified_maskfile_not_found { common::run_mask(&PathBuf::from("./nonexistent.md")) .command("what") .assert() + .code(1) .stderr(contains(format!( "{} specified maskfile not found", "ERROR:".red() diff --git a/tests/subcommands_test.rs b/tests/subcommands_test.rs index aab6bd6..278c6c0 100644 --- a/tests/subcommands_test.rs +++ b/tests/subcommands_test.rs @@ -49,6 +49,7 @@ fn exits_with_error_when_missing_subcommand() { common::run_mask(&maskfile_path) .assert() + .code(1) .stderr(contains( "error: 'mask' requires a subcommand, but one was not provided", )) @@ -141,6 +142,7 @@ echo "system, online" common::run_mask(&maskfile_path) .command("system") .assert() + .code(1) .stderr(contains( "error: 'mask system' requires a subcommand, but one was not provided", )) From 9ca9f1503e92e094d6aa964da2c83d9f0cf71a25 Mon Sep 17 00:00:00 2001 From: Jake Deichert Date: Sun, 6 Oct 2019 14:56:19 -0400 Subject: [PATCH 4/8] Adjust test command pattern flag --- maskfile.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/maskfile.md b/maskfile.md index 12c54b6..74ebcbe 100644 --- a/maskfile.md +++ b/maskfile.md @@ -54,10 +54,10 @@ cargo install --force --path . > Run all tests **OPTIONS** -* pattern - * flags: -p --pattern +* file + * flags: -f --file * type: string - * desc: Test only a specific file pattern + * desc: Only run tests from a specific filename ~~~sh extra_args="" @@ -68,12 +68,12 @@ if [[ "$verbose" == "true" ]]; then fi echo "Start tests..." -# Run all tests by default -if [[ "$pattern" == "" ]]; then +if [[ -z "$file" ]]; then + # Run all tests by default cargo test $extra_args else - # Tests a specific integration filename pattern - cargo test --test $pattern $extra_args + # Tests a specific integration filename + cargo test --test $file $extra_args fi ~~~ From 2e4ef6653f51c0c09fcdc305b120bc2dcc3a6628 Mon Sep 17 00:00:00 2001 From: Jake Deichert Date: Sun, 6 Oct 2019 15:51:23 -0400 Subject: [PATCH 5/8] Add init_script tests --- tests/init_script_test.rs | 81 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/init_script_test.rs diff --git a/tests/init_script_test.rs b/tests/init_script_test.rs new file mode 100644 index 0000000..a269dc3 --- /dev/null +++ b/tests/init_script_test.rs @@ -0,0 +1,81 @@ +use assert_cmd::prelude::*; +use predicates::str::contains; +use colored::*; + +mod common; +use common::MaskCommandExt; + +#[test] +fn prepares_the_subshell_with_the_init_script() { + let (_temp, maskfile_path) = common::maskfile( + r#" +**ON::INIT** +~~~sh +set -a # Export everything so subprocesses have access +TEST_VAR=123 +~~~ + +## run +~~~bash +echo "The test var is $TEST_VAR" +~~~ +"#, + ); + + common::run_mask(&maskfile_path) + .cli("run") + .assert() + .stdout(contains("The test var is 123")) + .success(); +} + +#[test] +fn exits_with_error_status_when_init_script_fails() { + let (_temp, maskfile_path) = common::maskfile( + r#" +**ON::INIT** +~~~sh +exit 1 +~~~ + +## run +~~~bash +echo "This shouldn't echo" +~~~ +"#, + ); + + common::run_mask(&maskfile_path) + .cli("run") + .assert() + .code(1) + .failure(); +} + +#[test] +fn exits_with_error_when_not_a_shell_based_executor() { + let (_temp, maskfile_path) = common::maskfile( + r#" +**ON::INIT** +~~~js +console.log("nope"); +~~~ + +## run +~~~bash +echo "This shouldn't echo" +~~~ +"#, + ); + + common::run_mask(&maskfile_path) + .cli("run") + .assert() + .code(1) + .stderr(contains(format!( + "{} ON::INIT must be a shell-based script executor.", + "ERROR:".red() + ))) + .failure(); +} + From 8cda8a12ff7f0edba15d5cefd92e735fd9da3421 Mon Sep 17 00:00:00 2001 From: Jake Deichert Date: Sun, 6 Oct 2019 15:58:32 -0400 Subject: [PATCH 6/8] Add ON::INIT helpers --- maskfile.md | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/maskfile.md b/maskfile.md index 74ebcbe..15521e1 100644 --- a/maskfile.md +++ b/maskfile.md @@ -19,7 +19,7 @@ * flags: -w --watch * desc: Rebuild on file change -~~~sh +~~~bash if [[ $watch == "true" ]]; then watchexec --exts rs --restart "cargo run -- $maskfile_command" else @@ -33,7 +33,7 @@ fi > Build a release version of mask -~~~sh +~~~bash cargo build --release ~~~ @@ -59,7 +59,7 @@ cargo install --force --path . * type: string * desc: Only run tests from a specific filename -~~~sh +~~~bash extra_args="" if [[ "$verbose" == "true" ]]; then @@ -67,7 +67,7 @@ if [[ "$verbose" == "true" ]]; then extra_args="-- --nocapture --test-threads=1" fi -echo "Start tests..." +log_info "Running tests..." if [[ -z "$file" ]]; then # Run all tests by default cargo test $extra_args @@ -75,6 +75,7 @@ else # Tests a specific integration filename cargo test --test $file $extra_args fi +log_success "Tests passed!" ~~~ @@ -87,7 +88,7 @@ fi > Update the cargo dependencies -~~~sh +~~~bash cargo update ~~~ @@ -102,7 +103,7 @@ cargo update * flags: -c --check * desc: Show which files are not formatted correctly -~~~sh +~~~bash if [[ $check == "true" ]]; then cargo fmt --all -- --check else @@ -116,6 +117,33 @@ fi > Lint the project with clippy -~~~sh +~~~bash cargo clippy ~~~ + + + + + + + +**ON::INIT** + +This special script sets up the subshell environment before a command is executed. This is useful for global utilities and helpers. + +~~~bash +set -a # Export everything so subprocesses have access +color_reset=$(tput sgr0) +color_blue=$(tput setaf 4) +color_green=$(tput setaf 2) +color_yellow=$(tput setaf 3) +color_red=$(tput setaf 1) +log_info() { echo "$color_blue$1$color_reset"; } +log_success() { echo "$color_green$1$color_reset"; } +log_error() { echo "$color_red$1$color_reset"; } +log_warn() { echo "$color_yellow$1$color_reset"; } +set +a +set -e # Exit on error +# Export this so bash subshells inherit "set -e" +export SHELLOPTS +~~~ From 6dc76ef469a1063ea2d3b106ef49a4c2ca427828 Mon Sep 17 00:00:00 2001 From: Jake Deichert Date: Sun, 6 Oct 2019 16:48:32 -0400 Subject: [PATCH 7/8] Add info about init script --- README.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6289ed4..283ceee 100644 --- a/README.md +++ b/README.md @@ -217,9 +217,7 @@ Valid lang codes: py, python ~~~python import os - name = os.getenv("name", "WORLD") - print("Hello, " + name + "!") ~~~ @@ -232,7 +230,6 @@ Valid lang codes: rb, ruby ~~~ruby name = ENV["name"] || "WORLD" - puts "Hello, #{name}!" ~~~ @@ -243,7 +240,6 @@ puts "Hello, #{name}!" ~~~php $name = getenv("name") ?: "WORLD"; - echo "Hello, " . $name . "!\n"; ~~~ ``` @@ -341,6 +337,30 @@ alias wask="mask --maskfile ~/maskfile.md" wask ~~~ +### Subshell environment initialization script + +When specifying the special `ON::INIT` script, you can hook into the subshell initialization process to inject common helpers and utilities your commands share. This script only allows shell-based executors (`sh`, `bash`, `zsh`, etc...) and it **must not** be defined as a heading. + +**Example:** + +```markdown +**ON::INIT** + +~~~bash +set -a # Export everything so subprocesses have access +log_info() { echo "🔵 >> $1"; } +log_error() { echo "❌ >> $1"; } +log_success() { echo "✅ >> $1"; } +~~~ + +## test + +~~~bash +log_info "Running tests..." +cargo test || log_error "TESTS FAILED" +~~~ +``` + ### Environment variable utilities Inside of each script's execution environment, `mask` injects a few environment variable helpers that might come in handy. From 3a4dc4612aabc6c24374d8530bb9d157066ee24a Mon Sep 17 00:00:00 2001 From: Jake Deichert Date: Sun, 6 Oct 2019 16:53:10 -0400 Subject: [PATCH 8/8] Fix formatting --- tests/init_script_test.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/init_script_test.rs b/tests/init_script_test.rs index a269dc3..822fd5c 100644 --- a/tests/init_script_test.rs +++ b/tests/init_script_test.rs @@ -1,6 +1,6 @@ use assert_cmd::prelude::*; -use predicates::str::contains; use colored::*; +use predicates::str::contains; mod common; use common::MaskCommandExt; @@ -78,4 +78,3 @@ echo "This shouldn't echo" ))) .failure(); } -