Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add $MASK and $MASKFILE_DIR utility env variables #26

Merged
merged 13 commits into from
Jul 27, 2019
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@

<p align="center">
<img height="180" width="210" src="https://user-images.githubusercontent.com/1631044/61989571-aae27580-afff-11e9-8f8a-c9768ed7a6b8.png">
</p>


[![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]
Expand Down Expand Up @@ -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:**

Expand All @@ -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 <path_to_maskfile>`
# which guarantees your scripts will still work even if they are called from
# another directory.
$MASK db migrate
$MASK start
~~~
```

Expand Down
38 changes: 37 additions & 1 deletion src/executor.rs
Original file line number Diff line number Diff line change
@@ -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<ExitStatus> {
pub fn execute_command(cmd: Command, maskfile_path: String) -> Result<ExitStatus> {
let mut child = match cmd.executor.as_ref() {
"js" | "javascript" => {
let mut child = process::Command::new("node");
Expand Down Expand Up @@ -38,6 +42,8 @@ pub fn execute_command(cmd: Command) -> Result<ExitStatus> {
}
};

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);
Expand All @@ -52,3 +58,33 @@ pub fn execute_command(cmd: Command) -> Result<ExitStatus> {

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 <path> 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
}
8 changes: 4 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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,
Expand All @@ -40,7 +40,7 @@ fn main() {
}
}

fn find_maskfile() -> Result<String, String> {
fn find_maskfile() -> (Result<String, String>, String) {
let args: Vec<String> = env::args().collect();

let maybe_maskfile = args.get(1);
Expand Down Expand Up @@ -68,7 +68,7 @@ fn find_maskfile() -> Result<String, String> {
}
}

maskfile
(maskfile, maskfile_path.to_str().unwrap().to_string())
}

fn custom_maskfile_path_arg<'a, 'b>() -> Arg<'a, 'b> {
Expand Down
87 changes: 87 additions & 0 deletions tests/env_vars_test.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}