Skip to content

Commit

Permalink
Add cmd_lib::main attribute macro to log main() error by default
Browse files Browse the repository at this point in the history
  • Loading branch information
tao-guo committed Oct 31, 2023
1 parent 5214d18 commit 4a7cfc2
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 12 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,14 @@ run_cmd!(du -ah $dir | sort -hr | head -n 10)?;
// if any command fails, just return Err(...)
let file = "/tmp/f";
let keyword = "rust";
if run_cmd! {
run_cmd! {
cat ${file} | grep ${keyword};
echo "bad cmd" >&2;
ignore ls /nofile;
date;
ls oops;
cat oops;
}.is_err() {
// your error handling code
}
}?;
```

- run_fun! --> FunResult
Expand Down Expand Up @@ -183,6 +181,12 @@ It is using rust [log crate](https://crates.io/crates/log), and you can use your
logger implementation. Notice that if you don't provide any logger, it will use env_logger to print
messages from process's stderr.

You can also mark your `main()` function with `#[cmd_lib::main]`, which will log error from
main() by default. Like this:
```rust
[ERROR] FATAL: Running ["mkdir" "/tmp/folder with spaces"] exited with error; status code: 1
```

#### Builtin commands
##### cd
cd: set process current directory.
Expand Down
1 change: 1 addition & 0 deletions examples/dd_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct Opt {
file: String,
}

#[cmd_lib::main]
fn main() -> MainResult {
let Opt {
block_size,
Expand Down
1 change: 1 addition & 0 deletions examples/pipes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ fn rand() -> i32 {
run_fun!(bash -c r"echo $RANDOM").unwrap().parse().unwrap()
}

#[cmd_lib::main]
fn main() -> MainResult {
// simple pre-check of TERM, tput's error message should be enough
let term = std::env::var("TERM").unwrap();
Expand Down
2 changes: 2 additions & 0 deletions examples/rust_cookbook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
//
use cmd_lib::*;
use std::io::{BufRead, BufReader};

#[cmd_lib::main]
fn main() -> MainResult {
cmd_lib::set_pipefail(false); // do not fail due to pipe errors

Expand Down
1 change: 1 addition & 0 deletions examples/tetris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ fn cmd_exit() {
std::process::exit(0);
}

#[cmd_lib::main]
fn main() -> MainResult {
#[rustfmt::skip]
let old_cfg = run_fun!(stty -g)?; // let's save terminal state ...
Expand Down
40 changes: 37 additions & 3 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@ use proc_macro2::{Span, TokenStream, TokenTree};
use proc_macro_error::{abort, proc_macro_error};
use quote::{quote, ToTokens};

/// Mark main function to log error result by default
///
/// ```
/// # use cmd_lib::*;
///
/// #[cmd_lib::main]
/// fn main() -> MainResult {
/// run_cmd!(bad_cmd)?;
/// Ok(())
/// }
/// // output:
/// // [ERROR] FATAL: Running ["bad_cmd"] failed: No such file or directory (os error 2)
/// ```
#[proc_macro_attribute]
pub fn main(
_args: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let orig_function: syn::ItemFn = syn::parse2(item.into()).unwrap();
let orig_main_block = orig_function.block;

quote! (
fn main() {
__cmd_main().unwrap_or_else(|err| {
::cmd_lib::error!("FATAL: {err:?}");
std::process::exit(1);
});
}

fn __cmd_main() -> MainResult {
#orig_main_block
}
)
.into()
}

/// Export the function as an command to be run by `run_cmd!` or `run_fun!`
///
/// ```
Expand Down Expand Up @@ -210,9 +246,7 @@ pub fn spawn_with_output(input: proc_macro::TokenStream) -> proc_macro::TokenStr
pub fn cmd_die(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let msg = parse_msg(input.into());
quote!({
use ::cmd_lib::error;
use ::cmd_lib::AsOsStr;
error!("FATAL: {}", #msg);
::cmd_lib::error!("FATAL: {}", #msg);
std::process::exit(1)
})
.into()
Expand Down
14 changes: 9 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,14 @@
//! // if any command fails, just return Err(...)
//! let file = "/tmp/f";
//! let keyword = "rust";
//! if run_cmd! {
//! run_cmd! {
//! cat ${file} | grep ${keyword};
//! echo "bad cmd" >&2;
//! ignore ls /nofile;
//! date;
//! ls oops;
//! cat oops;
//! }.is_err() {
//! // your error handling code
//! }
//! }?;
//! # Ok::<(), std::io::Error>(())
//! ```
//!
Expand Down Expand Up @@ -202,6 +200,12 @@
//! logger implementation. Notice that if you don't provide any logger, it will use env_logger to print
//! messages from process's stderr.
//!
//! You can also mark your `main()` function with `#[cmd_lib::main]`, which will log error from
//! main() by default. Like this:
//! ```
//! [ERROR] FATAL: Running ["mkdir" "/tmp/folder with spaces"] exited with error; status code: 1
//! ```
//!
//! ### Builtin commands
//! #### cd
//! cd: set process current directory.
Expand Down Expand Up @@ -356,7 +360,7 @@
//!
pub use cmd_lib_macros::{
cmd_die, export_cmd, run_cmd, run_fun, spawn, spawn_with_output, use_custom_cmd,
cmd_die, export_cmd, main, run_cmd, run_fun, spawn, spawn_with_output, use_custom_cmd,
};
/// Return type for run_fun!() macro
pub type FunResult = std::io::Result<String>;
Expand Down

0 comments on commit 4a7cfc2

Please sign in to comment.