Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bpaf"
version = "0.9.24"
version = "0.9.25"
edition = "2021"
categories = ["command-line-interface"]
description = "A simple Command Line Argument Parser with parser combinators"
Expand Down
6 changes: 6 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## bpaf [0.9.25] - 2026-04-15
- Change rendering of an adjacent block in Markdown - this is no longer a `###`
but a regular line item instead. Header messes up with generated navigation on
some pages
- `app_name` - parser that extracts the executable name

## bpaf [0.9.24] - 2026-03-13
- a less confusing error message when invalid user input mixes with parsers that
can succeed with no input, see #442
Expand Down
7 changes: 7 additions & 0 deletions docs2/src/appname/cases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Parsed application name doesn't show up in the `--help` output

> --help

but simply produces the app name, when it is available

> --user=Bob
18 changes: 18 additions & 0 deletions docs2/src/appname/combine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
use bpaf::*;
#[derive(Debug, Clone)]
pub struct Options {
user: String,
appname: String,
}

pub fn options() -> OptionParser<Options> {
let user = short('u')
.long("user")
.help("Specify user name")
// you can specify exact type argument should produce
// for as long as it implements `FromStr`
.argument::<String>("NAME");

construct!(Options { user, appname() }).to_options()
}
13 changes: 13 additions & 0 deletions docs2/src/appname/derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
use bpaf::*;
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Specify user name
#[bpaf(short, long, argument::<String>("NAME"))]
user: String,

/// Specify user age
#[bpaf(external)]
appname: String,
}
22 changes: 22 additions & 0 deletions examples/app_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use bpaf::*;
#[derive(Debug, Clone)]
pub struct Options {
user: String,
appname: String,
}

pub fn options() -> OptionParser<Options> {
let user = short('u')
.long("user")
.help("Specify user name")
// you can specify exact type argument should produce
// for as long as it implements `FromStr`
.argument::<String>("NAME");

construct!(Options { user, appname() }).to_options()
}

fn main() {
let opts = options().run();
println!("{opts:?}");
}
4 changes: 4 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ pub(crate) enum Block {
/// level 3 section header, "group_help" header, etc.
Section3,

// 2 margin
/// Same as level 3, but with no `###` in markdown
Section4,

// inline, 4 margin, no nesting
/// -h, --help
ItemTerm,
Expand Down
3 changes: 2 additions & 1 deletion src/buffer/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ impl Doc {
pending_newline = true;
margins.push(margin);
}
Block::Section3 => {
Block::Section3 | Block::Section4 => {
pending_newline = true;
margins.push(margin + 2);
}
Expand Down Expand Up @@ -310,6 +310,7 @@ impl Doc {
Block::Header
| Block::Section2
| Block::Section3
| Block::Section4
| Block::ItemTerm
| Block::DefinitionList
| Block::Meta
Expand Down
14 changes: 10 additions & 4 deletions src/buffer/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ impl Doc {
res.push_str("<p>");
}
Block::Meta => todo!(),
Block::Section3 => res.push_str("<div style='padding-left: 0.5em'>"),
Block::Section3 | Block::Section4 => {
res.push_str("<div style='padding-left: 0.5em'>")
}
Block::Mono | Block::TermRef => {}
Block::InlineBlock => {
skip.push();
Expand Down Expand Up @@ -294,7 +296,7 @@ impl Doc {
res.push_str("</p>");
}
Block::Mono | Block::TermRef => {}
Block::Section3 => res.push_str("</div>"),
Block::Section3 | Block::Section4 => res.push_str("</div>"),
Block::Meta => todo!(),
}
}
Expand Down Expand Up @@ -395,7 +397,7 @@ impl Doc {
Block::Section2 => {
res.push_str("");
}
Block::ItemTerm => {
Block::ItemTerm | Block::Section4 => {
new_markdown_line(&mut res);
empty_term = matches!(
self.tokens.get(ix + 1),
Expand Down Expand Up @@ -431,7 +433,11 @@ impl Doc {
Token::BlockEnd(b) => {
change_to_markdown_style(&mut res, &mut cur_style, Styles::default());
match b {
Block::Header | Block::Block | Block::Section3 | Block::Section2 => {
Block::Header
| Block::Block
| Block::Section3
| Block::Section2
| Block::Section4 => {
res.push('\n');
}

Expand Down
4 changes: 2 additions & 2 deletions src/buffer/manpage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ impl Doc {
Token::BlockStart(block) => {
//
match block {
Block::Header | Block::Section2 | Block::Section3 => {
Block::Header | Block::Section2 | Block::Section3 | Block::Section4 => {
capture.1 = true;
}
Block::ItemTerm => {
Expand Down Expand Up @@ -218,7 +218,7 @@ impl Doc {
roff.control("SH", [capture.0.to_uppercase()]);
capture.0.clear();
}
Block::Section2 | Block::Section3 => {
Block::Section2 | Block::Section3 | Block::Section4 => {
capture.1 = false;
roff.control("SS", [capture.0.to_uppercase()]);
capture.0.clear();
Expand Down
85 changes: 85 additions & 0 deletions src/docs2/appname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<details><summary>Combinatoric example</summary>

```no_run
#[derive(Debug, Clone)]
pub struct Options {
user: String,
appname: String,
}

pub fn options() -> OptionParser<Options> {
let user = short('u')
.long("user")
.help("Specify user name")
// you can specify exact type argument should produce
// for as long as it implements `FromStr`
.argument::<String>("NAME");

construct!(Options { user, appname() }).to_options()
}

fn main() {
println!("{:?}", options().run())
}
```

</details>
<details><summary>Derive example</summary>

```no_run
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Specify user name
#[bpaf(short, long, argument::<String>("NAME"))]
user: String,

/// Specify user age
#[bpaf(external)]
appname: String,
}

fn main() {
println!("{:?}", options().run())
}
```

</details>
<details><summary>Output</summary>

Parsed application name doesn't show up in the `--help` output


<div class='bpaf-doc'>
$ app --help<br>
<p><b>Usage</b>: <tt><b>app</b></tt> <tt><b>-u</b></tt>=<tt><i>NAME</i></tt></p><p><div>
<b>Available options:</b></div><dl><dt><tt><b>-u</b></tt>, <tt><b>--user</b></tt>=<tt><i>NAME</i></tt></dt>
<dd>Specify user name</dd>
<dt><tt><b>-h</b></tt>, <tt><b>--help</b></tt></dt>
<dd>Prints help information</dd>
</dl>
</p>
<style>
div.bpaf-doc {
padding: 14px;
background-color:var(--code-block-background-color);
font-family: "Source Code Pro", monospace;
margin-bottom: 0.75em;
}
div.bpaf-doc dt { margin-left: 1em; }
div.bpaf-doc dd { margin-left: 3em; }
div.bpaf-doc dl { margin-top: 0; padding-left: 1em; }
div.bpaf-doc { padding-left: 1em; }
</style>
</div>


but simply produces the app name, when it is available


<div class='bpaf-doc'>
$ app --user=Bob<br>
Options { user: "Bob", appname: "app" }
</div>

</details>
27 changes: 27 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1523,3 +1523,30 @@ pub fn choice<T: 'static>(parsers: impl IntoIterator<Item = Box<dyn Parser<T>>>)
}
this
}

/// Parse the application name
///
/// If you are using [`OptionParser::run`] then it should just work. If you are using
/// some variation of [`OptionParser::run_inner`] you'll need to set the application
/// name using [`Args::set_name`].
///
#[cfg_attr(not(doctest), doc = include_str!("docs2/appname.md"))]
pub fn appname() -> impl Parser<String> {
ParseAppname
}

#[derive(Debug, Copy, Clone)]
struct ParseAppname;
impl Parser<String> for ParseAppname {
fn eval(&self, args: &mut State) -> Result<String, Error> {
Ok(args
.path
.first()
.cloned()
.unwrap_or_else(|| String::from("app")))
}

fn meta(&self) -> Meta {
Meta::Skip
}
}
32 changes: 16 additions & 16 deletions src/meta_help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ pub(crate) enum HelpItem<'a> {
env: Option<&'static str>,
help: Option<&'a Doc>,
},
AnywhereStart {
AdjacentStart {
inner: &'a Meta,
ty: HiTy,
},
AnywhereStop {
AdjacentStop {
ty: HiTy,
},
}
Expand All @@ -70,8 +70,8 @@ impl HelpItem<'_> {
| HelpItem::Argument { help, .. } => help.is_some(),
HelpItem::GroupStart { .. } | HelpItem::DecorSuffix { .. } => true,
HelpItem::GroupEnd { .. }
| HelpItem::AnywhereStart { .. }
| HelpItem::AnywhereStop { .. } => false,
| HelpItem::AdjacentStart { .. }
| HelpItem::AdjacentStop { .. } => false,
}
}

Expand All @@ -80,8 +80,8 @@ impl HelpItem<'_> {
HelpItem::GroupStart { ty, .. }
| HelpItem::DecorSuffix { ty, .. }
| HelpItem::GroupEnd { ty }
| HelpItem::AnywhereStart { ty, .. }
| HelpItem::AnywhereStop { ty } => *ty,
| HelpItem::AdjacentStart { ty, .. }
| HelpItem::AdjacentStop { ty } => *ty,
HelpItem::Any {
anywhere: false, ..
}
Expand Down Expand Up @@ -131,15 +131,15 @@ impl<'a, 'b> Iterator for HelpItemsIter<'a, 'b> {
self.cur += 1;

let keep = match item {
HelpItem::AnywhereStart { ty, .. } => {
HelpItem::AdjacentStart { ty, .. } => {
self.block = ItemBlock::Anywhere(*ty);
*ty == self.target
}
HelpItem::GroupStart { ty, .. } => {
self.block = ItemBlock::Decor(*ty);
*ty == self.target
}
HelpItem::GroupEnd { ty, .. } | HelpItem::AnywhereStop { ty, .. } => {
HelpItem::GroupEnd { ty, .. } | HelpItem::AdjacentStop { ty, .. } => {
self.block = ItemBlock::No;
*ty == self.target
}
Expand Down Expand Up @@ -206,12 +206,12 @@ impl<'a> HelpItems<'a> {
}
Meta::Adjacent(m) => {
if let Some(ty) = m.peek_front_ty() {
hi.items.push(HelpItem::AnywhereStart {
hi.items.push(HelpItem::AdjacentStart {
inner: m.as_ref(),
ty,
});
go(hi, m, no_ss);
hi.items.push(HelpItem::AnywhereStop { ty });
hi.items.push(HelpItem::AdjacentStop { ty });
}
}
Meta::CustomUsage(x, _)
Expand Down Expand Up @@ -488,12 +488,12 @@ fn write_help_item(buf: &mut Doc, item: &HelpItem, include_env: bool) {
buf.token(Token::BlockEnd(Block::ItemBody));
}
}
HelpItem::AnywhereStart { inner, .. } => {
buf.token(Token::BlockStart(Block::Section3));
HelpItem::AdjacentStart { inner, .. } => {
buf.token(Token::BlockStart(Block::Section4));
buf.write_meta(inner, true);
buf.token(Token::BlockEnd(Block::Section3));
buf.token(Token::BlockEnd(Block::Section4));
}
HelpItem::AnywhereStop { .. } => {
HelpItem::AdjacentStop { .. } => {
buf.token(Token::BlockStart(Block::Block));
buf.token(Token::BlockEnd(Block::Block));
}
Expand Down Expand Up @@ -582,8 +582,8 @@ impl Dedup {
HelpItem::DecorSuffix { .. } => std::mem::take(&mut self.keep),
HelpItem::GroupStart { .. }
| HelpItem::GroupEnd { .. }
| HelpItem::AnywhereStart { .. }
| HelpItem::AnywhereStop { .. } => {
| HelpItem::AdjacentStart { .. }
| HelpItem::AdjacentStop { .. } => {
self.keep = true;
true
}
Expand Down
Loading
Loading