Skip to content

Commit

Permalink
Merge pull request #38 from jakedeichert/remove-init-script
Browse files Browse the repository at this point in the history
Remove the ON::INIT script idea
  • Loading branch information
jacobdeichert authored Oct 13, 2019
2 parents 057daa1 + de48807 commit 2a627df
Show file tree
Hide file tree
Showing 6 changed files with 22 additions and 245 deletions.
24 changes: 0 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,30 +336,6 @@ alias wask="mask --maskfile ~/maskfile.md"
wask <subcommand>
~~~

### Subshell environment initialization

Adding the special `ON::INIT` script allows you to hook into the subshell initialization process to inject common environment variables 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.
Expand Down
31 changes: 2 additions & 29 deletions maskfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ if [[ "$verbose" == "true" ]]; then
extra_args="-- --nocapture --test-threads=1"
fi

log_info "Running tests..."
echo "Running tests..."
if [[ -z "$file" ]]; then
# Run all tests by default
cargo test $extra_args
else
# Tests a specific integration filename
cargo test --test $file $extra_args
fi
log_success "Tests passed!"
echo "Tests passed!"
~~~


Expand Down Expand Up @@ -120,30 +120,3 @@ fi
~~~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
~~~
63 changes: 4 additions & 59 deletions src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@ use std::process::ExitStatus;

use clap::crate_name;

use crate::command::{Command, Script};
use crate::command::Command;

pub fn execute_command(
init_script: Script,
cmd: Command,
maskfile_path: String,
) -> Result<ExitStatus> {
pub fn execute_command(cmd: Command, maskfile_path: String) -> Result<ExitStatus> {
if cmd.script.source == "" {
let msg = "Command has no script.";
return Err(Error::new(ErrorKind::Other, msg));
Expand All @@ -24,24 +20,14 @@ pub fn execute_command(
return Err(Error::new(ErrorKind::Other, msg));
}

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);
}

let mut child = prepare_command(&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 {
fn prepare_command(cmd: &Command) -> process::Command {
let executor = cmd.script.executor.clone();
let source = cmd.script.source.clone();

Expand Down Expand Up @@ -76,47 +62,6 @@ fn prepare_command_without_init_script(cmd: &Command) -> process::Command {
}
}

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"),
// Any other executor that supports -c (sh, bash, zsh, fish, dash, etc...)
_ => run_with_init_script(&init_script, &cmd, &format!("{} -c", executor)),
}
}

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
fn add_utility_variables(mut child: process::Command, maskfile_path: String) -> process::Command {
let maskfile_path = PathBuf::from(maskfile_path);
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ fn main() {
return;
}

let (root_command, init_script) = mask::parser::build_command_structure(maskfile.unwrap());
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)
.expect("SubcommandRequired failed to work");

match execute_command(init_script, chosen_cmd, maskfile_path) {
match execute_command(chosen_cmd, maskfile_path) {
Ok(status) => match status.code() {
Some(code) => std::process::exit(code),
None => return,
Expand Down
65 changes: 14 additions & 51 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@ use pulldown_cmark::{
Options, Parser, Tag,
};

use crate::command::{Command, OptionFlag, RequiredArg, Script};
use crate::command::{Command, OptionFlag, RequiredArg};

// 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) {
pub fn build_command_structure(maskfile_contents: String) -> Command {
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 {
Expand All @@ -30,11 +28,7 @@ pub fn build_command_structure(maskfile_contents: String) -> (Command, Script) {
current_command = Command::new(heading_level as u8);
}
Tag::CodeBlock(lang_code) => {
if init_script_state.is_open() {
init_script.executor = lang_code.to_string();
} else {
current_command.script.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"
Expand All @@ -59,12 +53,7 @@ pub fn build_command_structure(maskfile_contents: String) -> (Command, Script) {
current_command.desc = text.clone();
}
Tag::CodeBlock(_) => {
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();
}
current_command.script.source = text.to_string();
}
Tag::List(_) => {
// Don't go lower than zero (for cases where it's a non-OPTIONS list)
Expand All @@ -84,12 +73,8 @@ pub fn build_command_structure(maskfile_contents: String) -> (Command, Script) {
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
else if list_level == 1 {
if list_level == 1 {
current_option_flag.name = text.clone();
}
// Options level 2 is the flag config
Expand Down Expand Up @@ -147,29 +132,7 @@ pub fn build_command_structure(maskfile_contents: String) -> (Command, Script) {
let root_command = all.first().expect("root command must exist");

// 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
}
root_command.clone()
}

fn create_markdown_parser<'a>(maskfile_contents: &'a String) -> Parser<'a> {
Expand Down Expand Up @@ -299,7 +262,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()
Expand All @@ -310,7 +273,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()
Expand All @@ -321,7 +284,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()
Expand All @@ -333,7 +296,7 @@ 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()
Expand All @@ -344,7 +307,7 @@ mod build_command_structure {

#[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()
Expand All @@ -358,7 +321,7 @@ mod build_command_structure {

#[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()
Expand All @@ -372,7 +335,7 @@ mod build_command_structure {

#[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()
Expand All @@ -393,7 +356,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()
Expand Down
Loading

0 comments on commit 2a627df

Please sign in to comment.