Skip to content

Commit

Permalink
Merge pull request pacak#166 from pacak/exit_code
Browse files Browse the repository at this point in the history
Release 0.7.9
  • Loading branch information
pacak authored Feb 14, 2023
2 parents 6c22fd3 + cdc289e commit a9a63c8
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 20 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bpaf"
version = "0.7.8"
version = "0.7.9"
edition = "2021"
categories = ["command-line-interface"]
description = "A simple Command Line Argument Parser with parser combinators"
Expand All @@ -14,7 +14,7 @@ exclude = [".github/workflows", "tarp.sh"]


[dependencies]
bpaf_derive = { path = "./bpaf_derive", version = "=0.3.3", optional = true }
bpaf_derive = { path = "./bpaf_derive", version = "=0.3.4", optional = true }
owo-colors = { version = "3.5.0", features = ["supports-colors"], optional = true }
roff = { version = "0.2.1", optional = true }

Expand Down
6 changes: 5 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Change Log

## bpaf [0.7.8] - Unreleased
## bpaf [0.7.9], bpaf_derive [0.3.4] - 2023-02-14
- `ParseFailure::exit_code`
- A way to specify custom usage in derive macro

## bpaf [0.7.8] - 2023-01-01
- manpage generation bugfixes,
thanks to @ysndr
- internal cleanups
Expand Down
2 changes: 1 addition & 1 deletion bpaf_derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bpaf_derive"
version = "0.3.3"
version = "0.3.4"
edition = "2018"
categories = ["command-line-interface"]
description = "Derive macros for bpaf Command Line Argument Parser"
Expand Down
41 changes: 36 additions & 5 deletions bpaf_derive/src/top.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ struct Decor {
header: Option<String>,
footer: Option<String>,
version: Option<Box<Expr>>,
usage: Option<LitStr>,
}

impl ToTokens for Decor {
Expand All @@ -80,6 +81,9 @@ impl ToTokens for Decor {
if let Some(ver) = &self.version {
quote!(.version(#ver)).to_tokens(tokens);
}
if let Some(usage) = &self.usage {
quote!(.usage(#usage)).to_tokens(tokens);
}
}
}

Expand Down Expand Up @@ -121,6 +125,7 @@ pub struct CommandAttr {
name: LitStr,
shorts: Vec<LitChar>,
longs: Vec<LitStr>,
usage: Option<LitStr>,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -169,6 +174,7 @@ impl Inner {
name,
shorts: Vec::new(),
longs: Vec::new(),
usage: None,
});
} else if keyword == "short" {
let lit = parse_opt_arg::<LitChar>(input)?.unwrap_or_else(|| {
Expand All @@ -191,6 +197,15 @@ impl Inner {
res.is_default = true;
} else if keyword == "skip" {
res.skip = true;
} else if keyword == "usage" {
if let Some(cmd) = &mut res.command {
let content;
let _ = parenthesized!(content in input);
cmd.usage = Some(content.parse::<LitStr>()?);
} else {
return Err(input_copy
.error("In this context `usage` requires `command` annotation"));
}
} else {
return Err(input_copy.error("Not a valid inner attribute"));
}
Expand Down Expand Up @@ -238,6 +253,7 @@ impl Outer {
adjacent: false,
};

let mut usage = None;
let mut help = Vec::new();
for attr in attrs {
if attr.path.is_ident("doc") {
Expand Down Expand Up @@ -282,7 +298,17 @@ impl Outer {
name,
shorts: Vec::new(),
longs: Vec::new(),
usage: None,
}));
} else if keyword == "usage" {
let content;
let _ = parenthesized!(content in input);
let lit = Some(content.parse::<LitStr>()?);
if let Some(OuterKind::Command(cmd)) = &mut res.kind {
cmd.usage = lit;
} else {
usage = lit;
}
} else if keyword == "short" {
// those are aliaes, no fancy name figuring out logic
let lit = parse_lit_char(input)?;
Expand Down Expand Up @@ -310,7 +336,7 @@ impl Outer {
cmd.longs.append(&mut res.longs);
}

res.decor = Decor::new(&help, res.version.take());
res.decor = Decor::new(&help, res.version.take(), usage);

Ok(res)
}
Expand Down Expand Up @@ -426,7 +452,7 @@ impl Top {

if !inner.skip {
if let Some(cmd_arg) = inner.command {
let decor = Decor::new(&inner.help, None);
let decor = Decor::new(&inner.help, None, None);
let oparser = OParser {
inner: Box::new(branch),
decor,
Expand Down Expand Up @@ -518,14 +544,18 @@ impl ToTokens for BParser {
names = quote!(#names .long(#long));
}

let usage = match &cmd_attr.usage {
Some(usage) => quote!(.usage(#usage)),
None => quote!(),
};
if let Some(msg) = &oparser.decor.descr {
quote!( {
let inner_cmd = #oparser;
let inner_cmd = #oparser #usage;
::bpaf::command(#cmd_name, inner_cmd).help(#msg)#names
})
} else {
quote!({
let inner_cmd = #oparser;
let inner_cmd = #oparser #usage;
::bpaf::command(#cmd_name, inner_cmd)#names
})
}
Expand Down Expand Up @@ -646,13 +676,14 @@ impl Fields {
}

impl Decor {
fn new(help: &[String], version: Option<Box<Expr>>) -> Self {
fn new(help: &[String], version: Option<Box<Expr>>, usage: Option<LitStr>) -> Self {
let mut iter = LineIter::from(help);
Decor {
descr: iter.next(),
header: iter.next(),
footer: iter.next(),
version,
usage,
}
}
}
26 changes: 24 additions & 2 deletions bpaf_derive/src/top_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,28 @@ fn top_struct_options1() {
assert_eq!(top.to_token_stream().to_string(), expected.to_string());
}

#[test]
fn options_with_custom_usage() {
let top: Top = parse_quote! {
#[bpaf(options, usage("App: {usage}"))]
struct Opt {}
};

let expected = quote! {
fn opt() -> ::bpaf::OptionParser<Opt> {
#[allow (unused_imports)]
use ::bpaf::Parser;
{
::bpaf::construct!(Opt {})
}
.to_options()
.usage("App: {usage}")
}
};

assert_eq!(top.to_token_stream().to_string(), expected.to_string());
}

#[test]
fn struct_options2() {
let input: Top = parse_quote! {
Expand Down Expand Up @@ -280,7 +302,7 @@ fn enum_to_flag_and_switches() {
BarFoo,
Baz(#[bpaf(long("bazz"))] String),
Strange { strange: String },
#[bpaf(command("alpha"))]
#[bpaf(command("alpha"), usage("custom"))]
Alpha,
#[bpaf(command)]
Omega,
Expand All @@ -304,7 +326,7 @@ fn enum_to_flag_and_switches() {
::bpaf::construct!(Opt::Strange { strange })
};
let alt5 = {
let inner_cmd = ::bpaf::pure(Opt::Alpha).to_options();
let inner_cmd = ::bpaf::pure(Opt::Alpha).to_options().usage("custom");
::bpaf::command("alpha", inner_cmd)
};
let alt6 = {
Expand Down
36 changes: 36 additions & 0 deletions examples/very_custom_usage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! A way to customize usage for a nested command
//!
//! Usually you would go with generated usage or by overriding it using `usage` attribute
//! to the top level or command level bpaf annotation. By taking advantage of command being just a
//! set of options with it's own help message and a custom prefix you can override the usage with
//! an arbitrary string, including one generated at runtime by doing something like this:
use bpaf::*;

// this defines top level set of options and refers to an external parser `cmd_usage`
// At this point cmd_usage can be any parser that produces Cmd
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
enum Opts {
Cmd(#[bpaf(external(cmd_usage))] Cmd),
}

// bpaf defines command as something with its own help message that can be accessed with a
// positional command name - inside of the command there is an OptionParser, this struct
// defines the parser we are going to use later
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
struct Cmd {
opt: bool,
}

// At this point we have OptionParser<Cmd> and we want to turn that into a regular parser
// with custom usage string - for that we are using two functions from combinatoric api:
// `usage` and `command`
fn cmd_usage() -> impl Parser<Cmd> {
cmd().usage("A very custom usage goes here").command("cmd")
}

fn main() {
println!("{:?}", opts().run());
}
26 changes: 26 additions & 0 deletions src/batteries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,29 @@ where
.hide();
construct!(skip, parser).map(|x| x.1)
}

/// Get usage for a parser
///
/// In some cases you might want to print usage if user gave no command line options, in this case
/// you should add an enum variant to a top level enum, make it hidden with `#[bpaf(hide)]`, make
/// it default for the top level parser with something like `#[bpaf(fallback(Arg::Help))]`.
///
/// When handling cases you can do something like this for `Help` variant:
///
/// ```ignore
/// ...
/// Arg::Help => {
/// println!("{}", get_usage(parser()));
/// std::process::exit(0);
/// }
/// ...
/// ```
pub fn get_usage<T>(parser: crate::OptionParser<T>) -> String
where
T: std::fmt::Debug,
{
parser
.run_inner(crate::Args::from(&["--help"]))
.unwrap_err()
.unwrap_stdout()
}
19 changes: 10 additions & 9 deletions src/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,7 @@ impl<T> OptionParser<T> {
{
match self.try_run() {
Ok(t) => t,
Err(ParseFailure::Stdout(msg)) => {
print!("{}", msg); // completions are sad otherwise
std::process::exit(0);
}
Err(ParseFailure::Stderr(msg)) => {
eprintln!("{}", msg);
std::process::exit(1);
}
Err(err) => std::process::exit(err.exit_code()),
}
}

Expand Down Expand Up @@ -599,7 +592,15 @@ impl<T> OptionParser<T> {
///
/// # Derive usage
///
/// Not available directly, but you can call `usage` on generated [`OptionParser`].
/// ```rust
/// # use bpaf::*;
/// #[derive(Debug, Clone, Bpaf)]
/// #[bpaf(options, usage("Usage: my_program: {usage}"))]
/// struct Options {
/// #[bpaf(short)]
/// switch: bool
/// }
/// ```
#[must_use]
pub fn usage(mut self, usage: &'static str) -> Self {
self.info.usage = Some(usage);
Expand Down
16 changes: 16 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1643,6 +1643,22 @@ impl ParseFailure {
}
}
}

/// Run an action appropriate to the failure and produce the exit code
///
/// Prints a message to `stdout` or `stderr` and returns the exit code
pub fn exit_code(self) -> i32 {
match self {
ParseFailure::Stdout(msg) => {
print!("{}", msg); // completions are sad otherwise
0
}
ParseFailure::Stderr(msg) => {
eprintln!("{}", msg);
1
}
}
}
}

/// Strip a command name if present at the front when used as a `cargo` command
Expand Down

0 comments on commit a9a63c8

Please sign in to comment.