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 ability to check exit status of command that was run #19

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,39 @@ At the moment of writing, there are examples using:
- Node
- Rust

Often, it is helpful to ensure that the commands being run successfully complete
or at least return the expected exit code. This check is supported through an
optional flag after `cmdrun` but before your command.
Any errors encountered by cmdrun are reported in the rendered mdbook.
For example, the following source

````markdown
<!-- cmdrun -0 echo hello world -->
```diff
<!-- cmdrun -0 diff a.rs b.rs -->
```
```diff
<!-- cmdrun -1 diff a.rs b.rs -->
```
````
gets rendered as
````markdown
hello world
```diff
**cmdrun error**: 'diff a.rs b.rs' returned exit code 1 instead of 0.
```
```diff
2c2
< println!("I'm from `a.rs`");
---
> println!("I'm from `b.rs`");
```
````
The available flags for specifying the exit code are
- `-N` where `N` is the integer exit code that the command should return
- `--strict` requires the command to return 0
- `--expect-return-code N` requires the command to return code `N`


## Contributors

Expand Down
116 changes: 108 additions & 8 deletions src/cmdrun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,122 @@ impl CmdRun {
}
}

fn cmdrun_error_message(message: &str, command: &str) -> String {
format!("**cmdrun error**: {} in 'cmdrun {}'", message, command)
}

// This method is public for unit tests
pub fn run_cmdrun(command: String, working_dir: &str, inline: bool) -> Result<String> {
// unfortunately, we need to manually parse the command string for cmdrun's
// exit status checking flags.
// Some experimentation using clap was done; however, splitting and then re-escaping
// the shellwords was found to be a large barrier to using this other tool.
let (command, correct_exit_code): (String, Option<i32>) =
if let Some(first_word) = command.split_whitespace().next() {
if first_word.starts_with('-') {
if first_word.starts_with("--") {
// double-tick long form
match first_word {
"--strict" => (
command
.split_whitespace()
.skip(1)
.collect::<Vec<&str>>()
.join(" "),
Some(0),
),
"--expect-return-code" => {
if let Some(second_word) = command.split_whitespace().nth(1) {
match second_word.parse::<i32>() {
Ok(return_code) => (
command
.split_whitespace()
.skip(2)
.collect::<Vec<&str>>()
.join(" "),
Some(return_code),
),
Err(_) => {
return Ok(Self::cmdrun_error_message(
"No return code after '--expect-return-code'",
&command,
));
}
}
} else {
// no second word after return code, print error
return Ok(Self::cmdrun_error_message(
"No return code after '--expect-return-code'",
&command,
));
}
}
some_other_word => {
// unrecognized flag, print error
return Ok(Self::cmdrun_error_message(
&format!("Unrecognized cmdrun flag {}", some_other_word),
&command,
));
}
}
} else {
// single-tick short form
let (_, exit_code) = first_word.rsplit_once('-').unwrap_or(("", "0"));
match exit_code.parse::<i32>() {
Ok(return_code) => (
command
.split_whitespace()
.skip(1)
.collect::<Vec<&str>>()
.join(" "),
Some(return_code),
),
Err(_) => {
return Ok(Self::cmdrun_error_message(
&format!(
"Unable to interpret short-form exit code {} as a number",
first_word
),
&command,
));
}
}
}
} else {
(command, None)
}
} else {
(command, None)
};

let output = Command::new(LAUNCH_SHELL_COMMAND)
.args([LAUNCH_SHELL_FLAG, &command])
.current_dir(working_dir)
.output()
.with_context(|| "Fail to run shell")?;

let stdout = Self::format_whitespace(String::from_utf8_lossy(&output.stdout), inline);

// let stderr = String::from_utf8_lossy(&output.stderr).to_string();

// eprintln!("command: {}", command);
// eprintln!("stdout: {:?}", stdout);
// eprintln!("stderr: {:?}", stderr);

Ok(stdout)
match (output.status.code(), correct_exit_code) {
(None, _) => Ok(Self::cmdrun_error_message(
"Command was ended before completing",
&command,
)),
(Some(code), Some(correct_code)) => {
if code != correct_code {
Ok(format!(
"**cmdrun error**: '{command}' returned exit code {code} instead of {correct_code}.\n{0}\n{1}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)))
} else {
Ok(stdout)
}
}
(Some(_code), None) => {
// no correct code specified, program exited with some code _code
// could put default check requiring code to be zero here but
// that would break current behavior
Ok(stdout)
}
}
}
}
60 changes: 60 additions & 0 deletions tests/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,63 @@ cfg_if! {

}
}

// the inline flag only affects the output and here I'm just checking exit codes
// so I only test without the inline flag
// I am also not testing more complicated input/output strings and so it is
// the same between Windoze and Unix
add_test!(pass_without_exit_code_spec, "exit 1", "", false);
add_test!(short_match_fail_exit_code, "-1 exit 1", "", false);
add_test!(short_match_pass_exit_code, "-0 exit 0", "", false);
add_test!(
short_exit_code_mismatch,
"-0 exit 1",
"**cmdrun error**: 'exit 1' returned exit code 1 instead of 0.\n\n",
false
);
add_test!(
long_match_fail_exit_code,
"--expect-return-code 1 exit 1",
"",
false
);
add_test!(
long_match_pass_exit_code1,
"--expect-return-code 0 exit 0",
"",
false
);
add_test!(long_match_pass_exit_code2, "--strict exit 0", "", false);
add_test!(
long_exit_code_mismatch1,
"--expect-return-code 0 exit 1",
"**cmdrun error**: 'exit 1' returned exit code 1 instead of 0.\n\n",
false
);
add_test!(
long_exit_code_mismatch2,
"--strict exit 1",
"**cmdrun error**: 'exit 1' returned exit code 1 instead of 0.\n\n",
false
);
add_test!(
not_a_cmdrun_flag,
"--flag-dne echo hello world",
"**cmdrun error**: Unrecognized cmdrun flag --flag-dne in 'cmdrun --flag-dne echo hello world'",
false
);
add_test!(
shortform_typo,
"--0 echo hello world",
"**cmdrun error**: Unrecognized cmdrun flag --0 in 'cmdrun --0 echo hello world'",
false
);
add_test!(missing_arg_no_cmd, "--expect-return-code",
"**cmdrun error**: No return code after '--expect-return-code' in 'cmdrun --expect-return-code'",
false);
add_test!(missing_arg_no_code, "--expect-return-code echo hello world",
"**cmdrun error**: No return code after '--expect-return-code' in 'cmdrun --expect-return-code echo hello world'",
false);
add_test!(bad_short_form_exit_code, "-NaN echo hello world",
"**cmdrun error**: Unable to interpret short-form exit code -NaN as a number in 'cmdrun -NaN echo hello world'",
false);
2 changes: 2 additions & 0 deletions tests/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ fn check_all_regressions_dirs() {
entries,
vec![
"bash_call",
"err_messages",
"inline_call",
"py_factorial",
"py_fibonacci",
Expand All @@ -90,3 +91,4 @@ add_dir!(py_factorial);
add_dir!(py_fibonacci);
add_dir!(rust_call);
add_dir!(shell);
add_dir!(err_messages);
7 changes: 7 additions & 0 deletions tests/regression/err_messages/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Error Messages

<!-- cmdrun --0 echo simple typo -->

<!-- cmdrun eco simple typo that will inject empty line -->

<!-- cmdrun -0 eco simple typo that will now inject error message -->
1 change: 1 addition & 0 deletions tests/regression/err_messages/input_win.md
7 changes: 7 additions & 0 deletions tests/regression/err_messages/output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Error Messages

**cmdrun error**: Unrecognized cmdrun flag --0 in 'cmdrun --0 echo simple typo '

**cmdrun error**: 'eco simple typo that will now inject error message' returned exit code 127 instead of 0.

sh: 1: eco: not found
1 change: 1 addition & 0 deletions tests/regression/err_messages/output_win.md