diff --git a/.circleci/config.yml b/.circleci/config.yml index 1347d2c..93c9476 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,6 +42,9 @@ jobs: steps: - *attach_workspace - *restore_cache + - run: + name: Make mask available for certain test suites that depend on it + command: echo 'export PATH="$PATH:~/repo/target/debug"' >> $BASH_ENV - run: name: Install latest node from PPA command: apt-get update && apt-get install -y curl software-properties-common && curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt-get install nodejs diff --git a/README.md b/README.md index 7ef3f56..4836159 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ -

- + [![build status](https://img.shields.io/circleci/build/github/jakedeichert/mask/master.svg)][circleci] [![mask version](https://img.shields.io/crates/v/mask.svg)][crate] @@ -263,7 +262,7 @@ ARGS: ### Running mask from within a script -You can easily call `mask` within scripts if you need to chain commands together. +You can easily call `mask` within scripts if you need to chain commands together. However, if you plan on [running mask with a different maskfile](#running-mask-with-a-different-maskfile), you should consider using the `$MASK` utility instead which allows your scripts to be location-agnostic. **Example:** @@ -276,8 +275,11 @@ You can easily call `mask` within scripts if you need to chain commands together mask install mask build mask link -mask db migrate -mask start +# $MASK also works. It's an alias variable for `mask --maskfile ` +# which guarantees your scripts will still work even if they are called from +# another directory. +$MASK db migrate +$MASK start ~~~ ``` diff --git a/src/executor.rs b/src/executor.rs index b9b9076..6e945c6 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,10 +1,14 @@ +use std::fs::canonicalize; use std::io::Result; +use std::path::{Path, PathBuf}; use std::process; use std::process::ExitStatus; +use clap::crate_name; + use crate::command::Command; -pub fn execute_command(cmd: Command) -> Result { +pub fn execute_command(cmd: Command, maskfile_path: String) -> Result { let mut child = match cmd.executor.as_ref() { "js" | "javascript" => { let mut child = process::Command::new("node"); @@ -38,6 +42,8 @@ pub fn execute_command(cmd: Command) -> Result { } }; + 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); @@ -52,3 +58,33 @@ pub fn execute_command(cmd: Command) -> Result { child.spawn()?.wait() } + +// Add some useful environment variables that scripts can use +fn add_utility_variables(mut child: process::Command, maskfile_path: String) -> process::Command { + let maskfile_path = PathBuf::from(maskfile_path); + + // Find the absolute path to the maskfile + let absolute_path = canonicalize(&maskfile_path) + .expect("canonicalize maskfile path failed") + .to_str() + .unwrap() + .to_string(); + let absolute_path = Path::new(&absolute_path); + let absolute_path_str = absolute_path.to_str().unwrap(); + + // Find the absolute path to the maskfile's parent directory + let parent_dir = absolute_path.parent().unwrap().to_str().unwrap(); + + // This allows us to call "$MASK command" instead of "mask --maskfile command" + // inside scripts so that they can be location-agnostic (not care where they are + // called from). This is useful for global maskfiles especially. + child.env( + "MASK", + format!("{} --maskfile {}", crate_name!(), absolute_path_str), + ); + // This allows us to refer to the directory the maskfile lives in which can be handy + // for loading relative files to it. + child.env("MASKFILE_DIR", parent_dir); + + child +} diff --git a/src/main.rs b/src/main.rs index d3609b2..e91ed45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ fn main() { .about(crate_description!()) .arg(custom_maskfile_path_arg()); - let maskfile = find_maskfile(); + let (maskfile, maskfile_path) = find_maskfile(); if maskfile.is_err() { // If the maskfile can't be found, at least parse for --version or --help cli_app.get_matches(); @@ -31,7 +31,7 @@ fn main() { std::process::exit(1); } - match mask::executor::execute_command(chosen_cmd.unwrap()) { + match mask::executor::execute_command(chosen_cmd.unwrap(), maskfile_path) { Ok(status) => match status.code() { Some(code) => std::process::exit(code), None => return, @@ -40,7 +40,7 @@ fn main() { } } -fn find_maskfile() -> Result { +fn find_maskfile() -> (Result, String) { let args: Vec = env::args().collect(); let maybe_maskfile = args.get(1); @@ -68,7 +68,7 @@ fn find_maskfile() -> Result { } } - maskfile + (maskfile, maskfile_path.to_str().unwrap().to_string()) } fn custom_maskfile_path_arg<'a, 'b>() -> Arg<'a, 'b> { diff --git a/tests/env_vars_test.rs b/tests/env_vars_test.rs new file mode 100644 index 0000000..48ed484 --- /dev/null +++ b/tests/env_vars_test.rs @@ -0,0 +1,87 @@ +use assert_cmd::prelude::*; +use predicates::str::contains; + +mod common; +use common::MaskCommandExt; + +// NOTE: This test suite depends on the mask binary being available in the current shell + +// Using current_dir(".github") to make sure the default maskfile.md can't be found +mod env_var_mask { + use super::*; + + #[test] + fn works_from_any_dir() { + let (_temp, maskfile_path) = common::maskfile( + r#" +## ci + +~~~bash +$MASK test +~~~ + +## test + +~~~bash +echo "tests passed" +~~~ +"#, + ); + + common::run_mask(&maskfile_path) + .current_dir(".github") + .command("ci") + .assert() + .stdout(contains("tests passed")) + .success(); + } + + #[test] + fn set_to_the_correct_value() { + let (_temp, maskfile_path) = common::maskfile( + r#" +## run + +~~~bash +echo "mask = $MASK" +~~~ +"#, + ); + + common::run_mask(&maskfile_path) + .current_dir(".github") + .command("run") + .assert() + // Absolute maskfile path starts with / + .stdout(contains("mask = mask --maskfile /")) + // And ends with maskfile.md + .stdout(contains("maskfile.md")) + .success(); + } +} + +// Using current_dir(".github") to make sure the default maskfile.md can't be found +mod env_var_maskfile_dir { + use super::*; + + #[test] + fn set_to_the_correct_value() { + let (_temp, maskfile_path) = common::maskfile( + r#" +## run + +~~~bash +echo "maskfile_dir = $MASKFILE_DIR" +~~~ +"#, + ); + + common::run_mask(&maskfile_path) + .current_dir(".github") + .command("run") + .assert() + // Absolute maskfile path starts with / + .stdout(contains("maskfile_dir = /")) + .success(); + } +}