From 396349d749e1ac0f0c3cf6e0c3d6e6f67bf5bff5 Mon Sep 17 00:00:00 2001 From: Michael Baikov Date: Sat, 15 Jul 2023 11:11:27 -0400 Subject: [PATCH 1/5] handle help_if_no_args inside run_subparser Smaller code but you also can put it on subcommand parsers --- src/args.rs | 22 ++++++---------------- src/info.rs | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/args.rs b/src/args.rs index 3e36025f..1f701ce4 100644 --- a/src/args.rs +++ b/src/args.rs @@ -185,7 +185,7 @@ pub use inner::State; mod inner { use std::{ops::Range, rc::Rc}; - use crate::{error::Message, parsers::NamedArg, Args}; + use crate::{error::Message, Args}; use super::{split_os_argument, Arg, ArgType, ItemState}; #[derive(Clone, Debug)] @@ -255,7 +255,6 @@ mod inner { short_flags: &[char], short_args: &[char], err: &mut Option, - help: Option<&NamedArg>, ) -> State { let mut items = Vec::new(); let mut pos_only = false; @@ -359,15 +358,6 @@ mod inner { } } } - if items.is_empty() { - if let Some(help) = help.and_then(|h| h.long.first()) { - items.push(Arg::Long( - (*help).to_owned(), - false, - std::ffi::OsString::new(), - )) - } - } let mut item_state = vec![ItemState::Unparsed; items.len()]; let mut remaining = items.len(); @@ -729,7 +719,7 @@ mod tests { fn from(value: &'static [&'static str; N]) -> Self { let args = Args::from(value); let mut msg = None; - let res = State::construct(args, &[], &[], &mut msg, None); + let res = State::construct(args, &[], &[], &mut msg); if let Some(err) = &msg { panic!("Couldn't construct state: {:?}/{:?}", err, res); } @@ -759,7 +749,7 @@ mod tests { fn multiple_short_flags() { let args = Args::from(&["-vvv"]); let mut err = None; - let mut a = State::construct(args, &['v'], &[], &mut err, None); + let mut a = State::construct(args, &['v'], &[], &mut err); assert!(a.take_flag(&short('v'))); assert!(a.take_flag(&short('v'))); assert!(a.take_flag(&short('v'))); @@ -883,7 +873,7 @@ mod tests { fn ambiguity_towards_flag() { let args = Args::from(&["-abc"]); let mut err = None; - let mut a = State::construct(args, &['a', 'b', 'c'], &[], &mut err, None); + let mut a = State::construct(args, &['a', 'b', 'c'], &[], &mut err); assert!(a.take_flag(&short('a'))); assert!(a.take_flag(&short('b'))); @@ -894,7 +884,7 @@ mod tests { fn ambiguity_towards_argument() { let args = Args::from(&["-abc"]); let mut err = None; - let mut a = State::construct(args, &[], &['a'], &mut err, None); + let mut a = State::construct(args, &[], &['a'], &mut err); let r = a.take_arg(&short('a'), false, M).unwrap().unwrap(); assert_eq!(r, "bc"); @@ -904,7 +894,7 @@ mod tests { fn ambiguity_towards_error() { let args = Args::from(&["-abc"]); let mut err = None; - let _a = State::construct(args, &['a', 'b', 'c'], &['a'], &mut err, None); + let _a = State::construct(args, &['a', 'b', 'c'], &['a'], &mut err); assert!(err.is_some()); } diff --git a/src/info.rs b/src/info.rs index e305e195..2e8dfa04 100644 --- a/src/info.rs +++ b/src/info.rs @@ -203,12 +203,7 @@ impl OptionParser { short_flags.extend(&self.info.version_arg.short); let args = args.into(); let mut err = None; - let help = if self.info.help_if_no_args { - Some(&self.info.help_arg) - } else { - None - }; - let mut state = State::construct(args, &short_flags, &short_args, &mut err, help); + let mut state = State::construct(args, &short_flags, &short_args, &mut err); // this only handles disambiguation failure in construct if let Some(msg) = err { @@ -232,6 +227,17 @@ impl OptionParser { // // outer parser gets value in ParseFailure format + if self.info.help_if_no_args && args.is_empty() { + let buffer = render_help( + &args.path, + &self.info, + &self.inner.meta(), + &self.info.meta(), + true, + ); + return Err(ParseFailure::Stdout(buffer, false)); + }; + let res = self.inner.eval(args); if let Err(Error(Message::ParseFailure(failure))) = res { return Err(failure); From a359f4693e4b6944827c4a56dd880074f31c93a4 Mon Sep 17 00:00:00 2001 From: Michael Baikov Date: Sat, 15 Jul 2023 11:12:38 -0400 Subject: [PATCH 2/5] docs --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 399ea2e0..c141e92c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -218,8 +218,9 @@ //! [`debug_fallback`](ParseFallback::debug_fallback) . //! - [`optional`](Parser::optional) - return `None` if value is missing instead of failing, see //! also [`catch`](ParseOptional::catch) . -//! - [`many`](Parser::many) and [`some`](Parser::some) - collect multiple values into a vector, -//! see their respective [`catch`](ParseMany::catch) and [`catch`](ParseSome). +//! - [`many`](Parser::many), [`some`](Parser::some) and [`collect`](Parser::collect) - collect +//! multiple values into a collection, usually a vector, see their respective +//! [`catch`](ParseMany::catch), [`catch`](ParseSome::catch) and [`catch`](ParseCollect::catch). //! - [`map`](Parser::map), [`parse`](Parser::parse) and [`guard`](Parser::guard) - transform //! and/or validate value produced by a parser //! - [`to_options`](Parser::to_options) - finalize the parser and prepare to run it From 0add6596674095a3c8cc61ae1605caf5a00b0d86 Mon Sep 17 00:00:00 2001 From: Michael Baikov Date: Mon, 17 Jul 2023 14:18:34 -0400 Subject: [PATCH 3/5] Opt in to generate-link-to-definition when building on docs.rs --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6616e02e..0bd2bfe0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ bpaf = { path = ".", features = ["derive", "extradocs", "autocomplete", "docgen [package.metadata.docs.rs] features = ["derive", "extradocs", "autocomplete", "batteries"] cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] -rustdoc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--generate-link-to-definition"] [features] From c4005ae360807898115df3472fbd6152e6bc7a7f Mon Sep 17 00:00:00 2001 From: Michael Baikov Date: Fri, 21 Jul 2023 11:36:12 -0400 Subject: [PATCH 4/5] Generate full example in docs2 --- docs2/build.rs | 6 +- docs2/src/command_enum/combine.rs | 1 + docs2/src/command_enum/derive.rs | 1 + docs2/src/lib.rs | 5 + src/docs2/adjacent_argument.md | 8 ++ src/docs2/adjacent_command.md | 8 ++ src/docs2/adjacent_struct_0.md | 8 ++ src/docs2/adjacent_struct_1.md | 8 ++ src/docs2/adjacent_struct_3.md | 8 ++ src/docs2/adjacent_struct_4.md | 4 + src/docs2/any_literal.md | 8 ++ src/docs2/any_simple.md | 8 ++ src/docs2/any_switch.md | 8 ++ src/docs2/argument.md | 8 ++ src/docs2/cargo_helper.md | 8 ++ src/docs2/collect.md | 8 ++ src/docs2/command.md | 8 ++ src/docs2/command_enum.md | 10 ++ src/docs2/complete.md | 8 ++ src/docs2/compose_basic_argument.md | 119 ++++++++++++++++++++++ src/docs2/compose_basic_choice.md | 103 +++++++++++++++++++ src/docs2/compose_basic_command.md | 137 ++++++++++++++++++++++++++ src/docs2/compose_basic_construct.md | 77 +++++++++++++++ src/docs2/compose_basic_many.md | 64 ++++++++++++ src/docs2/compose_basic_positional.md | 90 +++++++++++++++++ src/docs2/compose_basic_switch.md | 68 +++++++++++++ src/docs2/compose_basic_to_options.md | 107 ++++++++++++++++++++ src/docs2/count.md | 8 ++ src/docs2/custom_help_version.md | 4 + src/docs2/custom_usage.md | 8 ++ src/docs2/fallback.md | 8 ++ src/docs2/fallback_with.md | 8 ++ src/docs2/flag.md | 8 ++ src/docs2/group_help.md | 8 ++ src/docs2/guard.md | 8 ++ src/docs2/help.md | 8 ++ src/docs2/hide.md | 8 ++ src/docs2/hide_usage.md | 8 ++ src/docs2/intro.md | 80 +++++++++++++++ src/docs2/last.md | 8 ++ src/docs2/many.md | 8 ++ src/docs2/many_catch.md | 8 ++ src/docs2/map.md | 8 ++ src/docs2/named_arg_combine.md | 4 + src/docs2/named_arg_derive.md | 4 + src/docs2/optional.md | 8 ++ src/docs2/optional_catch.md | 8 ++ src/docs2/parse.md | 8 ++ src/docs2/positional.md | 8 ++ src/docs2/positional_strict.md | 8 ++ src/docs2/pure.md | 8 ++ src/docs2/pure_with.md | 8 ++ src/docs2/req_flag.md | 8 ++ src/docs2/short_long_env.md | 8 ++ src/docs2/some.md | 8 ++ src/docs2/some_catch.md | 8 ++ src/docs2/switch.md | 8 ++ src/docs2/switch_help.md | 8 ++ src/docs2/to_options.md | 8 ++ src/docs2/usage.md | 8 ++ src/docs2/with_group_help.md | 4 + src/docs2/with_usage.md | 4 + 62 files changed, 1227 insertions(+), 1 deletion(-) create mode 100644 src/docs2/compose_basic_argument.md create mode 100644 src/docs2/compose_basic_choice.md create mode 100644 src/docs2/compose_basic_command.md create mode 100644 src/docs2/compose_basic_construct.md create mode 100644 src/docs2/compose_basic_many.md create mode 100644 src/docs2/compose_basic_positional.md create mode 100644 src/docs2/compose_basic_switch.md create mode 100644 src/docs2/compose_basic_to_options.md create mode 100644 src/docs2/intro.md diff --git a/docs2/build.rs b/docs2/build.rs index 075ddc72..38da29c1 100644 --- a/docs2/build.rs +++ b/docs2/build.rs @@ -133,7 +133,11 @@ fn import_example(example: &Path, name: &str) -> Result { let test_source = std::fs::read_to_string(&example)?; let mut cases = String::new(); - for line in std::fs::read_to_string(PathBuf::from("src").join(name).join("cases.md"))?.lines() { + let file = PathBuf::from("src").join(name).join("cases.md"); + if !file.exists() { + return Err(format!("File does not exist: {file:?}").into()); + } + for line in std::fs::read_to_string(file)?.lines() { if let Some(all_args) = line.strip_prefix("> ") { let args = shell_words::split(all_args)?; write_scenario(&mut cases, &args, all_args, None)?; diff --git a/docs2/src/command_enum/combine.rs b/docs2/src/command_enum/combine.rs index bc3c7a0d..508747b7 100644 --- a/docs2/src/command_enum/combine.rs +++ b/docs2/src/command_enum/combine.rs @@ -14,6 +14,7 @@ pub enum Options { Build { /// Name of a binary to build bin: String, + /// Compile the binary in release mode release: bool, }, diff --git a/docs2/src/command_enum/derive.rs b/docs2/src/command_enum/derive.rs index a31c31a2..7f78cafb 100644 --- a/docs2/src/command_enum/derive.rs +++ b/docs2/src/command_enum/derive.rs @@ -20,6 +20,7 @@ pub enum Options { #[bpaf(argument("BIN"))] /// Name of a binary to build bin: String, + /// Compile the binary in release mode release: bool, }, diff --git a/docs2/src/lib.rs b/docs2/src/lib.rs index 02495df1..50395ce8 100644 --- a/docs2/src/lib.rs +++ b/docs2/src/lib.rs @@ -40,6 +40,11 @@ fn import_escaped_source(res: &mut String, path: impl AsRef, ti writeln!(res, "{}", line).unwrap(); } } + writeln!( + res, + "\nfn main() {{\n println!(\"{{:?}}\", options().run())\n}}" + ) + .unwrap(); writeln!(res, "```\n").unwrap(); writeln!(res, "").unwrap(); } diff --git a/src/docs2/adjacent_argument.md b/src/docs2/adjacent_argument.md index d31c71d2..d4e1c41b 100644 --- a/src/docs2/adjacent_argument.md +++ b/src/docs2/adjacent_argument.md @@ -17,6 +17,10 @@ fn package() -> impl Parser { pub fn options() -> OptionParser { construct!(Options { package() }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -30,6 +34,10 @@ pub struct Options { /// Package to use package: String, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/adjacent_command.md b/src/docs2/adjacent_command.md index e54a6e9f..387fc21b 100644 --- a/src/docs2/adjacent_command.md +++ b/src/docs2/adjacent_command.md @@ -50,6 +50,10 @@ pub fn options() -> OptionParser { let commands = cmd().many(); construct!(Options { premium, commands }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -84,6 +88,10 @@ pub enum Cmd { time: usize, }, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/adjacent_struct_0.md b/src/docs2/adjacent_struct_0.md index f260b373..db727933 100644 --- a/src/docs2/adjacent_struct_0.md +++ b/src/docs2/adjacent_struct_0.md @@ -34,6 +34,10 @@ pub fn options() -> OptionParser { let point = point().many(); construct!(Options { point, rotate }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -66,6 +70,10 @@ struct Point { /// Height of a point above the plane z: f64, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/adjacent_struct_1.md b/src/docs2/adjacent_struct_1.md index 608df694..e4135363 100644 --- a/src/docs2/adjacent_struct_1.md +++ b/src/docs2/adjacent_struct_1.md @@ -43,6 +43,10 @@ pub fn options() -> OptionParser { let rect = rect().many(); construct!(Options { rect, mirror }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -73,6 +77,10 @@ struct Rect { /// Should rectangle be filled? painted: bool, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/adjacent_struct_3.md b/src/docs2/adjacent_struct_3.md index d34ee4f6..2b998667 100644 --- a/src/docs2/adjacent_struct_3.md +++ b/src/docs2/adjacent_struct_3.md @@ -36,6 +36,10 @@ pub fn options() -> OptionParser { .switch(); construct!(Options { exec(), switch }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -82,6 +86,10 @@ fn is_semi() -> impl Parser<()> { fn execs() -> impl Parser>> { exec().map(|e| e.body).optional() } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/adjacent_struct_4.md b/src/docs2/adjacent_struct_4.md index 6924bb7a..78d00d17 100644 --- a/src/docs2/adjacent_struct_4.md +++ b/src/docs2/adjacent_struct_4.md @@ -46,6 +46,10 @@ pub fn options() -> OptionParser { let meal = meal().many(); construct!(Options { meal, premium }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/any_literal.md b/src/docs2/any_literal.md index 696ba1e0..cda12ce5 100644 --- a/src/docs2/any_literal.md +++ b/src/docs2/any_literal.md @@ -54,6 +54,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -122,6 +126,10 @@ where // type. [`parse`](Parser::parse) handles that .parse(|s| s.parse()) } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/any_simple.md b/src/docs2/any_simple.md index e6cd7531..c7ae14f6 100644 --- a/src/docs2/any_simple.md +++ b/src/docs2/any_simple.md @@ -17,6 +17,10 @@ pub fn options() -> OptionParser { .many(); construct!(Options { turbo, rest }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -41,6 +45,10 @@ fn not_help(s: OsString) -> Option { Some(s) } } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/any_switch.md b/src/docs2/any_switch.md index de32e5e0..5531047a 100644 --- a/src/docs2/any_switch.md +++ b/src/docs2/any_switch.md @@ -53,6 +53,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -105,6 +109,10 @@ fn backing() -> impl Parser { fn xinerama() -> impl Parser { toggle_option("xinerama", "enable or disable Xinerama") } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/argument.md b/src/docs2/argument.md index 40fb0bb6..aa8551a0 100644 --- a/src/docs2/argument.md +++ b/src/docs2/argument.md @@ -25,6 +25,10 @@ pub fn options() -> OptionParser { construct!(Options { name, age }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -45,6 +49,10 @@ pub struct Options { /// Specify user age age: usize, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/cargo_helper.md b/src/docs2/cargo_helper.md index 8cfbb80c..118ec462 100644 --- a/src/docs2/cargo_helper.md +++ b/src/docs2/cargo_helper.md @@ -16,6 +16,10 @@ pub fn options() -> OptionParser { cargo_helper("pretty", options).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -31,6 +35,10 @@ pub struct Options { #[bpaf(short)] switch: bool, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/collect.md b/src/docs2/collect.md index 3e238d5a..17865ac1 100644 --- a/src/docs2/collect.md +++ b/src/docs2/collect.md @@ -17,6 +17,10 @@ pub fn options() -> OptionParser { let switches = long("switch").help("some switch").switch().collect(); construct!(Options { argument, switches }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -35,6 +39,10 @@ pub struct Options { #[bpaf(long("switch"), switch, collect)] switches: BTreeSet, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/command.md b/src/docs2/command.md index f60762a3..3d8b1191 100644 --- a/src/docs2/command.md +++ b/src/docs2/command.md @@ -32,6 +32,10 @@ pub fn options() -> OptionParser { .switch(); construct!(Options { flag, cmd() }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -58,6 +62,10 @@ pub struct Options { #[bpaf(external)] cmd: Cmd, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/command_enum.md b/src/docs2/command_enum.md index e48a9222..39f27c48 100644 --- a/src/docs2/command_enum.md +++ b/src/docs2/command_enum.md @@ -15,6 +15,7 @@ pub enum Options { Build { /// Name of a binary to build bin: String, + /// Compile the binary in release mode release: bool, }, @@ -47,6 +48,10 @@ pub fn options() -> OptionParser { construct!([run, build]).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -73,10 +78,15 @@ pub enum Options { #[bpaf(argument("BIN"))] /// Name of a binary to build bin: String, + /// Compile the binary in release mode release: bool, }, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/complete.md b/src/docs2/complete.md index bcaddf3c..8b16b4c5 100644 --- a/src/docs2/complete.md +++ b/src/docs2/complete.md @@ -23,6 +23,10 @@ pub fn options() -> OptionParser { .complete(completer); construct!(Options { name }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -46,6 +50,10 @@ pub struct Options { /// Specify character's name name: String, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/compose_basic_argument.md b/src/docs2/compose_basic_argument.md new file mode 100644 index 00000000..363f5962 --- /dev/null +++ b/src/docs2/compose_basic_argument.md @@ -0,0 +1,119 @@ +
Combinatoric example + +```no_run +use bpaf::*; + +pub fn options() -> OptionParser { + short('s') + .long("size") + .help("Defines size of an object") + .argument::("SIZE") + .to_options() +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +By default all arguments are required so running with no arguments produces an error + + +
+$ app
+Error: expected --size=SIZE, pass --help for usage information + +
+ + +Bpaf accepts various combinations of names and adjacencies: + + +
+$ app -s100
+100 +
+ + +
+$ app --size 300
+300 +
+ + +
+$ app -s=42
+42 +
+ + +
+$ app --size=14
+14 +
+ + +Since not every string is a valid number - bpaf would report any parsing failures to the user +directly + + +
+$ app --size fifty
+Error: couldn't parse fifty: invalid digit found in string + +
+ + +In addition to the switch you defined `bpaf` generates switch for user help which will include +the description from the `help` method + + +
+$ app --help
+

Usage: app -s=SIZE

+Available options:
-s, --size=SIZE
+
Defines size of an object
+
-h, --help
+
Prints help information
+
+

+ +
+ +
\ No newline at end of file diff --git a/src/docs2/compose_basic_choice.md b/src/docs2/compose_basic_choice.md new file mode 100644 index 00000000..66f82503 --- /dev/null +++ b/src/docs2/compose_basic_choice.md @@ -0,0 +1,103 @@ +
Combinatoric example + +```no_run +use bpaf::*; + +pub fn options() -> OptionParser { + let miles = long("miles").help("Distance in miles").argument("MI"); + let km = long("kilo").help("Distance in kilometers").argument("KM"); + construct!([miles, km]).to_options() +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +Help message describes all the parser combined + +
+$ app --help
+

Usage: app (--miles=MI | --kilo=KM)

+Available options:
--miles=MI
+
Distance in miles
+
--kilo=KM
+
Distance in kilometers
+
-h, --help
+
Prints help information
+
+

+ +
+ + +Users can pass value that satisfy either parser + + +
+$ app --miles 42
+42.0 +
+ + +
+$ app --kilo 15
+15.0 +
+ + +But not both at once or not at all: + + +
+$ app --miles 53 --kilo 10
+Error: --kilo cannot be used at the same time as --miles + +
+ + +
+$ app
+Error: expected --miles=MI or --kilo=KM, pass --help for usage information + +
+ + +If those cases are valid you can handle them with `optional` and `many` +
\ No newline at end of file diff --git a/src/docs2/compose_basic_command.md b/src/docs2/compose_basic_command.md new file mode 100644 index 00000000..6ecd6a1f --- /dev/null +++ b/src/docs2/compose_basic_command.md @@ -0,0 +1,137 @@ +
Combinatoric example + +```no_run +#[derive(Debug, Clone)] +pub enum Options { + /// Run a binary + Run { + /// Name of a binary to run + bin: String, + + /// Arguments to pass to a binary + args: Vec, + }, + /// Compile a binary + Build { + /// Name of a binary to build + bin: String, + + /// Compile the binary in release mode + release: bool, + }, +} + +// combine mode gives more flexibility to share the same code across multiple parsers +fn run() -> impl Parser { + let bin = long("bin").help("Name of a binary to run").argument("BIN"); + let args = positional("ARG") + .strict() + .help("Arguments to pass to a binary") + .many(); + + construct!(Options::Run { bin, args }) +} + +pub fn options() -> OptionParser { + let run = run().to_options().descr("Run a binary").command("run"); + + let bin = long("bin") + .help("Name of a binary to build ") + .argument("BIN"); + let release = long("release") + .help("Compile the binary in release mode") + .switch(); + let build = construct!(Options::Build { bin, release }) + .to_options() + .descr("Compile a binary") + .command("build"); + + construct!([run, build]).to_options() +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +Help contains both commands, bpaf takes short command description from the inner command +description + + +
+$ app --help
+

Usage: app COMMAND ...

+Available options:
-h, --help
+
Prints help information
+
+

+Available commands:
run
+
Run a binary
+
build
+
Compile a binary
+
+

+ +
+ + +Same as before each command gets its own help message + + +
+$ app run --help
+

Run a binary

Usage: app run --bin=BIN -- [ARG]...

+Available positional items:
ARG
+
Arguments to pass to a binary
+
+

+Available options:
--bin=BIN
+
Name of a binary to run
+
-h, --help
+
Prints help information
+
+

+ +
+ + +And can be executed separately + + +
+$ app run --bin basic
+Run { bin: "basic", args: [] } +
+ + +
+$ app build --bin demo --release
+Build { bin: "demo", release: true } +
+ +
\ No newline at end of file diff --git a/src/docs2/compose_basic_construct.md b/src/docs2/compose_basic_construct.md new file mode 100644 index 00000000..569b7c39 --- /dev/null +++ b/src/docs2/compose_basic_construct.md @@ -0,0 +1,77 @@ +
Combinatoric example + +```no_run +use bpaf::*; + +#[derive(Debug, Clone)] +pub struct Options { + argument: Vec, + switches: Vec, +} + +pub fn options() -> OptionParser { + let argument = long("argument") + .help("important argument") + .argument("ARG") + .many(); + let switches = long("switch").help("some switch").switch().many(); + construct!(Options { argument, switches }).to_options() +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +Help message describes all the parser combined + +
+$ app --help
+

Usage: app [--argument=ARG]... [--switch]...

+Available options:
--argument=ARG
+
important argument
+
--switch
+
some switch
+
-h, --help
+
Prints help information
+
+

+ +
+ + +And users can pass any combinations of options, resulting parser will handle them as long as they are valid + + +
+$ app --argument 10 --argument 20
+Options { argument: [10, 20], switches: [false] } +
+ + +
+$ app --switch
+Options { argument: [], switches: [true] } +
+ + +
+$ app --switch --switch --argument 20
+Options { argument: [20], switches: [true, true] } +
+ +
\ No newline at end of file diff --git a/src/docs2/compose_basic_many.md b/src/docs2/compose_basic_many.md new file mode 100644 index 00000000..305f1c81 --- /dev/null +++ b/src/docs2/compose_basic_many.md @@ -0,0 +1,64 @@ +
Combinatoric example + +```no_run +use bpaf::*; + +pub fn options() -> OptionParser> { + let argument = long("argument") + .help("important argument") + .argument("ARG") + .many(); + argument.to_options() +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +Run inner parser as many times as possible collecting all the new results +First `false` is collected from a switch even if it is not consuming anything + +
+$ app --argument 10 --argument 20
+[10, 20] +
+ + +If there's no matching parameters - it would produce an empty vector. + +
+$ app
+[] +
+ + +In usage lines `many` items are indicated with `...` + +
+$ app --help
+

Usage: app [--argument=ARG]...

+Available options:
--argument=ARG
+
important argument
+
-h, --help
+
Prints help information
+
+

+ +
+ +
\ No newline at end of file diff --git a/src/docs2/compose_basic_positional.md b/src/docs2/compose_basic_positional.md new file mode 100644 index 00000000..609efc93 --- /dev/null +++ b/src/docs2/compose_basic_positional.md @@ -0,0 +1,90 @@ +
Combinatoric example + +```no_run +use bpaf::*; + +pub fn options() -> OptionParser { + let simple = positional("URL").help("Url to open"); + simple.to_options() +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +Same as with argument by default there's no fallback so with no arguments parser fails + + +
+$ app
+Error: expected URL, pass --help for usage information + +
+ + +Other than that any name that does not start with a dash or explicitly converted to positional +parameter gets parsed: + + +
+$ app https://lemmyrs.org
+"https://lemmyrs.org" +
+ + +
+$ app "strange url"
+"strange url" +
+ + +
+$ app -- --can-start-with-dash-too
+"--can-start-with-dash-too" +
+ + +And as usual there's help message + + +
+$ app --help
+

Usage: app URL

+Available positional items:
URL
+
Url to open
+
+

+Available options:
-h, --help
+
Prints help information
+
+

+ +
+ +
\ No newline at end of file diff --git a/src/docs2/compose_basic_switch.md b/src/docs2/compose_basic_switch.md new file mode 100644 index 00000000..c778e3ba --- /dev/null +++ b/src/docs2/compose_basic_switch.md @@ -0,0 +1,68 @@ +
Combinatoric example + +```no_run +use bpaf::*; + +pub fn options() -> OptionParser { + let simple = short('s').long("simple").switch(); + simple.to_options() +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +If you run the app with no parameters - switch will parse as `false` + + +
+$ app
+false +
+ + +Both short and long names produce true + + +
+$ app -s
+true +
+ + +
+$ app --simple
+true +
+ + +In addition to the switch you defined `bpaf` generates switch for user help + + +
+$ app --help
+

Usage: app [-s]

+Available options:
-s, --simple
+
-h, --help
+
Prints help information
+
+

+ +
+ +
\ No newline at end of file diff --git a/src/docs2/compose_basic_to_options.md b/src/docs2/compose_basic_to_options.md new file mode 100644 index 00000000..3ffe1495 --- /dev/null +++ b/src/docs2/compose_basic_to_options.md @@ -0,0 +1,107 @@ +
Combinatoric example + +```no_run +#[derive(Debug, Clone)] +pub struct Options { + argument: u32, +} + +pub fn options() -> OptionParser { + let argument = short('i').argument::("ARG"); + construct!(Options { argument }) + .to_options() + .version("3.1415") + .descr("This is a short description") + .header("It can contain multiple blocks, this block goes before options") + .footer("This one goes after") +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +In addition to all the arguments specified by user `bpaf` adds a few more. One of them is +`--help`: + + +
+$ app --help
+

This is a short description

Usage: app -i=ARG

It can contain multiple blocks, this block goes before options

+Available options:
-i=ARG
+
-h, --help
+
Prints help information
+
-V, --version
+
Prints version information
+
+

This one goes after

+ +
+ + +The other one is `--version` - passing a string literal or something like +`env!("CARGO_PKG_VERSION")` to get version from `cargo` directly usually works + + +
+$ app --version
+Version: 3.1415 + +
+ + +Other than that `bpaf` tries its best to provide a helpful error messages + + +
+$ app
+Error: expected -i=ARG, pass --help for usage information + +
+ + +And if all parsers are satisfied [`run`](OptionParser::run) produces the result + + +
+$ app -i 10
+Options { argument: 10 } +
+ +
\ No newline at end of file diff --git a/src/docs2/count.md b/src/docs2/count.md index 56aee2ac..58af8647 100644 --- a/src/docs2/count.md +++ b/src/docs2/count.md @@ -15,6 +15,10 @@ pub fn options() -> OptionParser { construct!(Options { verbosity }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -28,6 +32,10 @@ pub struct Options { #[bpaf(short('v'), long("verbose"), req_flag(()), count)] verbosity: usize, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/custom_help_version.md b/src/docs2/custom_help_version.md index 92d8c568..3ffc26a6 100644 --- a/src/docs2/custom_help_version.md +++ b/src/docs2/custom_help_version.md @@ -23,6 +23,10 @@ pub fn options() -> OptionParser { .help_parser(help) .version_parser(version) } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/custom_usage.md b/src/docs2/custom_usage.md index c9ee8c91..d40a22eb 100644 --- a/src/docs2/custom_usage.md +++ b/src/docs2/custom_usage.md @@ -18,6 +18,10 @@ pub struct Options { #[bpaf(short, long, argument("PACKAGE"))] package: Option, } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -50,6 +54,10 @@ pub fn options() -> OptionParser { construct!(Options { binary, package }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/fallback.md b/src/docs2/fallback.md index 8cd72c45..e7d019a1 100644 --- a/src/docs2/fallback.md +++ b/src/docs2/fallback.md @@ -14,6 +14,10 @@ pub fn options() -> OptionParser { .display_fallback(); construct!(Options { jobs }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -28,6 +32,10 @@ pub struct Options { #[bpaf(argument("JOBS"), fallback(42), display_fallback)] jobs: usize, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/fallback_with.md b/src/docs2/fallback_with.md index 37d2b77f..498ae7b3 100644 --- a/src/docs2/fallback_with.md +++ b/src/docs2/fallback_with.md @@ -18,6 +18,10 @@ pub fn options() -> OptionParser { .display_fallback(); construct!(Options { version }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -35,6 +39,10 @@ pub struct Options { /// Specify protocol version version: usize, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/flag.md b/src/docs2/flag.md index 2bd7ce8a..9aad4e1d 100644 --- a/src/docs2/flag.md +++ b/src/docs2/flag.md @@ -22,6 +22,10 @@ pub fn options() -> OptionParser { let decision = parse_decision(); construct!(Options { decision }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -41,6 +45,10 @@ pub enum Decision { Yes, No, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/group_help.md b/src/docs2/group_help.md index cfa5bb04..4d340842 100644 --- a/src/docs2/group_help.md +++ b/src/docs2/group_help.md @@ -35,6 +35,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -61,6 +65,10 @@ pub struct Options { #[bpaf(external, group_help("Takes a rectangle"))] rectangle: Rectangle, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/guard.md b/src/docs2/guard.md index b134b81f..cbaaceb3 100644 --- a/src/docs2/guard.md +++ b/src/docs2/guard.md @@ -13,6 +13,10 @@ pub fn options() -> OptionParser { ); construct!(Options { number }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -31,6 +35,10 @@ pub struct Options { #[bpaf(argument("N"), guard(dlc_check, DLC_NEEDED))] number: u32, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/help.md b/src/docs2/help.md index 0fc957a4..8a7eb0f1 100644 --- a/src/docs2/help.md +++ b/src/docs2/help.md @@ -17,6 +17,10 @@ pub fn options() -> OptionParser { .argument::("N"); construct!(Options { number }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -35,6 +39,10 @@ pub struct Options { #[bpaf(argument("N"), help(ARG))] number: u32, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/hide.md b/src/docs2/hide.md index 6dc6a9c0..fcda70fd 100644 --- a/src/docs2/hide.md +++ b/src/docs2/hide.md @@ -15,6 +15,10 @@ pub fn options() -> OptionParser { let switch = long("switch").help("secret switch").switch().hide(); construct!(Options { argument, switch }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -31,6 +35,10 @@ pub struct Options { #[bpaf(hide)] switch: bool, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/hide_usage.md b/src/docs2/hide_usage.md index 2f679702..d38ef91d 100644 --- a/src/docs2/hide_usage.md +++ b/src/docs2/hide_usage.md @@ -18,6 +18,10 @@ pub fn options() -> OptionParser { .hide_usage(); construct!(Options { argument, switch }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -35,6 +39,10 @@ pub struct Options { #[bpaf(hide_usage)] switch: bool, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/intro.md b/src/docs2/intro.md new file mode 100644 index 00000000..73f92212 --- /dev/null +++ b/src/docs2/intro.md @@ -0,0 +1,80 @@ +
Combinatoric example + +```no_run +use bpaf::*; + +#[derive(Debug, Clone)] +pub struct Options { + message: String, +} + +pub fn options() -> OptionParser { + let message = positional("MESSAGE").help("Message to print in a big friendly letters"); + construct!(Options { message }).to_options() +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Derive example + +```no_run +use bpaf::*; + +#[derive(Debug, Clone, Bpaf)] +#[bpaf(options)] +pub struct Options { + /// Message to print in a big friendly letters + #[bpaf(positional("MESSAGE"))] + message: String, +} + +fn main() { + println!("{:?}", options().run()) +} +``` + +
+
Output + +With everything in place users should be able to pass their arguments + + +
+$ app "Hello world"
+Options { message: "Hello world" } +
+ + +As well as read the help message generated by the library + + +
+$ app --help
+

Usage: app MESSAGE

+Available positional items:
MESSAGE
+
Message to print in a big friendly letters
+
+

+Available options:
-h, --help
+
Prints help information
+
+

+ +
+ +
\ No newline at end of file diff --git a/src/docs2/last.md b/src/docs2/last.md index be653b4d..ab1d1f1d 100644 --- a/src/docs2/last.md +++ b/src/docs2/last.md @@ -46,6 +46,10 @@ pub fn options() -> OptionParser { construct!(Options { style, report }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -86,6 +90,10 @@ pub struct Options { #[bpaf(external)] report: Report, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/many.md b/src/docs2/many.md index b326f86b..f8d45134 100644 --- a/src/docs2/many.md +++ b/src/docs2/many.md @@ -15,6 +15,10 @@ pub fn options() -> OptionParser { let switches = long("switch").help("some switch").switch().many(); construct!(Options { argument, switches }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -30,6 +34,10 @@ pub struct Options { #[bpaf(long("switch"), switch)] switches: Vec, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/many_catch.md b/src/docs2/many_catch.md index 57cfc918..f06d85cd 100644 --- a/src/docs2/many_catch.md +++ b/src/docs2/many_catch.md @@ -35,6 +35,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -58,6 +62,10 @@ pub struct Options { #[bpaf(long("width"), argument("PX"), many, hide)] width_str: Vec, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/map.md b/src/docs2/map.md index 97d742aa..81809e51 100644 --- a/src/docs2/map.md +++ b/src/docs2/map.md @@ -9,6 +9,10 @@ pub fn options() -> OptionParser { let number = long("number").argument::("N").map(|x| x * 2); construct!(Options { number }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -25,6 +29,10 @@ pub struct Options { #[bpaf(argument::("N"), map(twice_the_num))] number: u32, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/named_arg_combine.md b/src/docs2/named_arg_combine.md index f472940d..d97c4c79 100644 --- a/src/docs2/named_arg_combine.md +++ b/src/docs2/named_arg_combine.md @@ -40,6 +40,10 @@ pub fn options() -> OptionParser<(usize, Output, bool)> { construct!(size, output, verbose).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/named_arg_derive.md b/src/docs2/named_arg_derive.md index 35e59c5d..7974ed62 100644 --- a/src/docs2/named_arg_derive.md +++ b/src/docs2/named_arg_derive.md @@ -49,6 +49,10 @@ pub struct Options { #[bpaf(env("USER"))] pub user: String, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/optional.md b/src/docs2/optional.md index cc656bfc..0d11779c 100644 --- a/src/docs2/optional.md +++ b/src/docs2/optional.md @@ -11,6 +11,10 @@ pub fn options() -> OptionParser { let feature = long("feature").argument("FEAT").optional(); construct!(Options { version, feature }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -25,6 +29,10 @@ pub struct Options { #[bpaf(argument("FEAT"))] feature: Option, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/optional_catch.md b/src/docs2/optional_catch.md index faeaafdb..be92408a 100644 --- a/src/docs2/optional_catch.md +++ b/src/docs2/optional_catch.md @@ -35,6 +35,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -58,6 +62,10 @@ pub struct Options { #[bpaf(long("width"), argument("PX"), optional, hide)] width_str: Option, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/parse.md b/src/docs2/parse.md index cffb0165..4e3f3188 100644 --- a/src/docs2/parse.md +++ b/src/docs2/parse.md @@ -14,6 +14,10 @@ pub fn options() -> OptionParser { .parse::<_, _, ParseIntError>(|s| Ok(u32::from_str(&s)? * 2)); construct!(Options { number }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -30,6 +34,10 @@ pub struct Options { #[bpaf(argument::("N"), parse(twice_the_num))] number: u32, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/positional.md b/src/docs2/positional.md index 109f644f..5e653e4d 100644 --- a/src/docs2/positional.md +++ b/src/docs2/positional.md @@ -29,6 +29,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -52,6 +56,10 @@ pub struct Options { /// Display information about this feature feature_name: Option, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/positional_strict.md b/src/docs2/positional_strict.md index 39de1768..9bee23c3 100644 --- a/src/docs2/positional_strict.md +++ b/src/docs2/positional_strict.md @@ -25,6 +25,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -44,6 +48,10 @@ pub struct Options { /// Arguments for the binary args: Vec, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/pure.md b/src/docs2/pure.md index a41023a8..fd6ffe09 100644 --- a/src/docs2/pure.md +++ b/src/docs2/pure.md @@ -14,6 +14,10 @@ pub fn options() -> OptionParser { let money = pure(330); construct!(Options { name, money }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -29,6 +33,10 @@ pub struct Options { #[bpaf(pure(330))] money: u32, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/pure_with.md b/src/docs2/pure_with.md index c97b4bc4..0e50b5f0 100644 --- a/src/docs2/pure_with.md +++ b/src/docs2/pure_with.md @@ -18,6 +18,10 @@ pub fn options() -> OptionParser { let money = pure_with(starting_money); construct!(Options { name, money }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -37,6 +41,10 @@ pub struct Options { fn starting_money() -> Result { Ok(330) } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/req_flag.md b/src/docs2/req_flag.md index b93dee37..660f0ed1 100644 --- a/src/docs2/req_flag.md +++ b/src/docs2/req_flag.md @@ -54,6 +54,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -95,6 +99,10 @@ pub struct Options { #[bpaf(external)] report: Report, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/short_long_env.md b/src/docs2/short_long_env.md index 0774c388..2ea89d85 100644 --- a/src/docs2/short_long_env.md +++ b/src/docs2/short_long_env.md @@ -36,6 +36,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -55,6 +59,10 @@ pub struct Options { /// Custom user name username: String, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/some.md b/src/docs2/some.md index 5e213ab1..359d2496 100644 --- a/src/docs2/some.md +++ b/src/docs2/some.md @@ -18,6 +18,10 @@ pub fn options() -> OptionParser { .some("want at least one switch"); construct!(Options { argument, switches }).to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -34,6 +38,10 @@ pub struct Options { #[bpaf(long("switch"), req_flag(true), some("want at least one switch"))] switches: Vec, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/some_catch.md b/src/docs2/some_catch.md index bcea639c..066d4c2d 100644 --- a/src/docs2/some_catch.md +++ b/src/docs2/some_catch.md @@ -35,6 +35,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -58,6 +62,10 @@ pub struct Options { #[bpaf(long("width"), argument("PX"), many, hide)] width_str: Vec, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/switch.md b/src/docs2/switch.md index 64fc57f0..ce8aa820 100644 --- a/src/docs2/switch.md +++ b/src/docs2/switch.md @@ -29,6 +29,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -57,6 +61,10 @@ pub struct Options { #[bpaf(long("no-default-features"), flag(false, true))] default_features: bool, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/switch_help.md b/src/docs2/switch_help.md index 969d48ae..9cbd73e2 100644 --- a/src/docs2/switch_help.md +++ b/src/docs2/switch_help.md @@ -35,6 +35,10 @@ Output detailed help information, you can specify it multiple times }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -60,6 +64,10 @@ pub struct Options { /// Save output to a file output: Option, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/to_options.md b/src/docs2/to_options.md index f70b2752..5f6245d7 100644 --- a/src/docs2/to_options.md +++ b/src/docs2/to_options.md @@ -15,6 +15,10 @@ pub fn options() -> OptionParser { .header("It can contain multiple blocks, this block goes before options") .footer("This one goes after") } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -34,6 +38,10 @@ pub struct Options { #[bpaf(short('i'))] argument: u32, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/usage.md b/src/docs2/usage.md index 203f7963..0dbe5782 100644 --- a/src/docs2/usage.md +++ b/src/docs2/usage.md @@ -22,6 +22,10 @@ pub fn options() -> OptionParser { .to_options() .usage("Usage: my_program [--release] [--binary=BIN] ...") } + +fn main() { + println!("{:?}", options().run()) +} ``` @@ -38,6 +42,10 @@ pub struct Options { /// Use this binary binary: String, } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/with_group_help.md b/src/docs2/with_group_help.md index a0d24e42..a23fb4d2 100644 --- a/src/docs2/with_group_help.md +++ b/src/docs2/with_group_help.md @@ -45,6 +45,10 @@ pub fn options() -> OptionParser { }) .to_options() } + +fn main() { + println!("{:?}", options().run()) +} ``` diff --git a/src/docs2/with_usage.md b/src/docs2/with_usage.md index b48f70ed..546b3026 100644 --- a/src/docs2/with_usage.md +++ b/src/docs2/with_usage.md @@ -29,6 +29,10 @@ pub fn options() -> OptionParser { doc }) } + +fn main() { + println!("{:?}", options().run()) +} ``` From 321505da7c8e28726044055d96a339ad568fb70b Mon Sep 17 00:00:00 2001 From: Michael Baikov Date: Fri, 21 Jul 2023 12:03:02 -0400 Subject: [PATCH 5/5] Start user documentation overhaul --- docs2/src/compose_basic_argument/cases.md | 20 + docs2/src/compose_basic_argument/combine.rs | 9 + docs2/src/compose_basic_choice/cases.md | 14 + docs2/src/compose_basic_choice/combine.rs | 7 + docs2/src/compose_basic_command/cases.md | 13 + docs2/src/compose_basic_command/combine.rs | 49 + docs2/src/compose_basic_construct/cases.md | 8 + docs2/src/compose_basic_construct/combine.rs | 16 + docs2/src/compose_basic_many/cases.md | 9 + docs2/src/compose_basic_many/combine.rs | 9 + docs2/src/compose_basic_positional/cases.md | 14 + docs2/src/compose_basic_positional/combine.rs | 6 + docs2/src/compose_basic_switch/cases.md | 12 + docs2/src/compose_basic_switch/combine.rs | 6 + docs2/src/compose_basic_to_options/cases.md | 17 + docs2/src/compose_basic_to_options/combine.rs | 16 + docs2/src/intro/cases.md | 7 + docs2/src/intro/combine.rs | 11 + docs2/src/intro/derive.rs | 9 + src/_documentation.rs | 1110 +++++++++++++++++ src/lib.rs | 27 + 21 files changed, 1389 insertions(+) create mode 100644 docs2/src/compose_basic_argument/cases.md create mode 100644 docs2/src/compose_basic_argument/combine.rs create mode 100644 docs2/src/compose_basic_choice/cases.md create mode 100644 docs2/src/compose_basic_choice/combine.rs create mode 100644 docs2/src/compose_basic_command/cases.md create mode 100644 docs2/src/compose_basic_command/combine.rs create mode 100644 docs2/src/compose_basic_construct/cases.md create mode 100644 docs2/src/compose_basic_construct/combine.rs create mode 100644 docs2/src/compose_basic_many/cases.md create mode 100644 docs2/src/compose_basic_many/combine.rs create mode 100644 docs2/src/compose_basic_positional/cases.md create mode 100644 docs2/src/compose_basic_positional/combine.rs create mode 100644 docs2/src/compose_basic_switch/cases.md create mode 100644 docs2/src/compose_basic_switch/combine.rs create mode 100644 docs2/src/compose_basic_to_options/cases.md create mode 100644 docs2/src/compose_basic_to_options/combine.rs create mode 100644 docs2/src/intro/cases.md create mode 100644 docs2/src/intro/combine.rs create mode 100644 docs2/src/intro/derive.rs create mode 100644 src/_documentation.rs diff --git a/docs2/src/compose_basic_argument/cases.md b/docs2/src/compose_basic_argument/cases.md new file mode 100644 index 00000000..37778f36 --- /dev/null +++ b/docs2/src/compose_basic_argument/cases.md @@ -0,0 +1,20 @@ +By default all arguments are required so running with no arguments produces an error + +> + +Bpaf accepts various combinations of names and adjacencies: + +> -s100 +> --size 300 +> -s=42 +> --size=14 + +Since not every string is a valid number - bpaf would report any parsing failures to the user +directly + +> --size fifty + +In addition to the switch you defined `bpaf` generates switch for user help which will include +the description from the `help` method + +> --help diff --git a/docs2/src/compose_basic_argument/combine.rs b/docs2/src/compose_basic_argument/combine.rs new file mode 100644 index 00000000..1b2d05d3 --- /dev/null +++ b/docs2/src/compose_basic_argument/combine.rs @@ -0,0 +1,9 @@ +use bpaf::*; + +pub fn options() -> OptionParser { + short('s') + .long("size") + .help("Defines size of an object") + .argument::("SIZE") + .to_options() +} diff --git a/docs2/src/compose_basic_choice/cases.md b/docs2/src/compose_basic_choice/cases.md new file mode 100644 index 00000000..687d6414 --- /dev/null +++ b/docs2/src/compose_basic_choice/cases.md @@ -0,0 +1,14 @@ +Help message describes all the parser combined +> --help + +Users can pass value that satisfy either parser + +> --miles 42 +> --kilo 15 + +But not both at once or not at all: + +> --miles 53 --kilo 10 +> + +If those cases are valid you can handle them with `optional` and `many` diff --git a/docs2/src/compose_basic_choice/combine.rs b/docs2/src/compose_basic_choice/combine.rs new file mode 100644 index 00000000..3eb54f72 --- /dev/null +++ b/docs2/src/compose_basic_choice/combine.rs @@ -0,0 +1,7 @@ +use bpaf::*; + +pub fn options() -> OptionParser { + let miles = long("miles").help("Distance in miles").argument("MI"); + let km = long("kilo").help("Distance in kilometers").argument("KM"); + construct!([miles, km]).to_options() +} diff --git a/docs2/src/compose_basic_command/cases.md b/docs2/src/compose_basic_command/cases.md new file mode 100644 index 00000000..5abb5c25 --- /dev/null +++ b/docs2/src/compose_basic_command/cases.md @@ -0,0 +1,13 @@ +Help contains both commands, bpaf takes short command description from the inner command +description + +> --help + +Same as before each command gets its own help message + +> run --help + +And can be executed separately + +> run --bin basic +> build --bin demo --release diff --git a/docs2/src/compose_basic_command/combine.rs b/docs2/src/compose_basic_command/combine.rs new file mode 100644 index 00000000..508747b7 --- /dev/null +++ b/docs2/src/compose_basic_command/combine.rs @@ -0,0 +1,49 @@ +// +use bpaf::*; +#[derive(Debug, Clone)] +pub enum Options { + /// Run a binary + Run { + /// Name of a binary to run + bin: String, + + /// Arguments to pass to a binary + args: Vec, + }, + /// Compile a binary + Build { + /// Name of a binary to build + bin: String, + + /// Compile the binary in release mode + release: bool, + }, +} + +// combine mode gives more flexibility to share the same code across multiple parsers +fn run() -> impl Parser { + let bin = long("bin").help("Name of a binary to run").argument("BIN"); + let args = positional("ARG") + .strict() + .help("Arguments to pass to a binary") + .many(); + + construct!(Options::Run { bin, args }) +} + +pub fn options() -> OptionParser { + let run = run().to_options().descr("Run a binary").command("run"); + + let bin = long("bin") + .help("Name of a binary to build ") + .argument("BIN"); + let release = long("release") + .help("Compile the binary in release mode") + .switch(); + let build = construct!(Options::Build { bin, release }) + .to_options() + .descr("Compile a binary") + .command("build"); + + construct!([run, build]).to_options() +} diff --git a/docs2/src/compose_basic_construct/cases.md b/docs2/src/compose_basic_construct/cases.md new file mode 100644 index 00000000..fd695eec --- /dev/null +++ b/docs2/src/compose_basic_construct/cases.md @@ -0,0 +1,8 @@ +Help message describes all the parser combined +> --help + +And users can pass any combinations of options, resulting parser will handle them as long as they are valid + +> --argument 10 --argument 20 +> --switch +> --switch --switch --argument 20 diff --git a/docs2/src/compose_basic_construct/combine.rs b/docs2/src/compose_basic_construct/combine.rs new file mode 100644 index 00000000..f4473a29 --- /dev/null +++ b/docs2/src/compose_basic_construct/combine.rs @@ -0,0 +1,16 @@ +use bpaf::*; + +#[derive(Debug, Clone)] +pub struct Options { + argument: Vec, + switches: Vec, +} + +pub fn options() -> OptionParser { + let argument = long("argument") + .help("important argument") + .argument("ARG") + .many(); + let switches = long("switch").help("some switch").switch().many(); + construct!(Options { argument, switches }).to_options() +} diff --git a/docs2/src/compose_basic_many/cases.md b/docs2/src/compose_basic_many/cases.md new file mode 100644 index 00000000..8e8ab98c --- /dev/null +++ b/docs2/src/compose_basic_many/cases.md @@ -0,0 +1,9 @@ +Run inner parser as many times as possible collecting all the new results +First `false` is collected from a switch even if it is not consuming anything +> --argument 10 --argument 20 + +If there's no matching parameters - it would produce an empty vector. +> + +In usage lines `many` items are indicated with `...` +> --help diff --git a/docs2/src/compose_basic_many/combine.rs b/docs2/src/compose_basic_many/combine.rs new file mode 100644 index 00000000..cb06f429 --- /dev/null +++ b/docs2/src/compose_basic_many/combine.rs @@ -0,0 +1,9 @@ +use bpaf::*; + +pub fn options() -> OptionParser> { + let argument = long("argument") + .help("important argument") + .argument("ARG") + .many(); + argument.to_options() +} diff --git a/docs2/src/compose_basic_positional/cases.md b/docs2/src/compose_basic_positional/cases.md new file mode 100644 index 00000000..daae38ce --- /dev/null +++ b/docs2/src/compose_basic_positional/cases.md @@ -0,0 +1,14 @@ +Same as with argument by default there's no fallback so with no arguments parser fails + +> + +Other than that any name that does not start with a dash or explicitly converted to positional +parameter gets parsed: + +> https://lemmyrs.org +> "strange url" +> -- --can-start-with-dash-too + +And as usual there's help message + +> --help diff --git a/docs2/src/compose_basic_positional/combine.rs b/docs2/src/compose_basic_positional/combine.rs new file mode 100644 index 00000000..5d995777 --- /dev/null +++ b/docs2/src/compose_basic_positional/combine.rs @@ -0,0 +1,6 @@ +use bpaf::*; + +pub fn options() -> OptionParser { + let simple = positional("URL").help("Url to open"); + simple.to_options() +} diff --git a/docs2/src/compose_basic_switch/cases.md b/docs2/src/compose_basic_switch/cases.md new file mode 100644 index 00000000..7d6e42f5 --- /dev/null +++ b/docs2/src/compose_basic_switch/cases.md @@ -0,0 +1,12 @@ +If you run the app with no parameters - switch will parse as `false` + +> + +Both short and long names produce true + +> -s +> --simple + +In addition to the switch you defined `bpaf` generates switch for user help + +> --help diff --git a/docs2/src/compose_basic_switch/combine.rs b/docs2/src/compose_basic_switch/combine.rs new file mode 100644 index 00000000..f32368f2 --- /dev/null +++ b/docs2/src/compose_basic_switch/combine.rs @@ -0,0 +1,6 @@ +use bpaf::*; + +pub fn options() -> OptionParser { + let simple = short('s').long("simple").switch(); + simple.to_options() +} diff --git a/docs2/src/compose_basic_to_options/cases.md b/docs2/src/compose_basic_to_options/cases.md new file mode 100644 index 00000000..69964b14 --- /dev/null +++ b/docs2/src/compose_basic_to_options/cases.md @@ -0,0 +1,17 @@ +In addition to all the arguments specified by user `bpaf` adds a few more. One of them is +`--help`: + +> --help + +The other one is `--version` - passing a string literal or something like +`env!("CARGO_PKG_VERSION")` to get version from `cargo` directly usually works + +> --version + +Other than that `bpaf` tries its best to provide a helpful error messages + +> + +And if all parsers are satisfied [`run`](OptionParser::run) produces the result + +> -i 10 diff --git a/docs2/src/compose_basic_to_options/combine.rs b/docs2/src/compose_basic_to_options/combine.rs new file mode 100644 index 00000000..f3d5fe9e --- /dev/null +++ b/docs2/src/compose_basic_to_options/combine.rs @@ -0,0 +1,16 @@ +// +use bpaf::*; +#[derive(Debug, Clone)] +pub struct Options { + argument: u32, +} + +pub fn options() -> OptionParser { + let argument = short('i').argument::("ARG"); + construct!(Options { argument }) + .to_options() + .version("3.1415") + .descr("This is a short description") + .header("It can contain multiple blocks, this block goes before options") + .footer("This one goes after") +} diff --git a/docs2/src/intro/cases.md b/docs2/src/intro/cases.md new file mode 100644 index 00000000..dead5702 --- /dev/null +++ b/docs2/src/intro/cases.md @@ -0,0 +1,7 @@ +With everything in place users should be able to pass their arguments + +> "Hello world" + +As well as read the help message generated by the library + +> --help diff --git a/docs2/src/intro/combine.rs b/docs2/src/intro/combine.rs new file mode 100644 index 00000000..010f9c08 --- /dev/null +++ b/docs2/src/intro/combine.rs @@ -0,0 +1,11 @@ +use bpaf::*; + +#[derive(Debug, Clone)] +pub struct Options { + message: String, +} + +pub fn options() -> OptionParser { + let message = positional("MESSAGE").help("Message to print in a big friendly letters"); + construct!(Options { message }).to_options() +} diff --git a/docs2/src/intro/derive.rs b/docs2/src/intro/derive.rs new file mode 100644 index 00000000..8890d186 --- /dev/null +++ b/docs2/src/intro/derive.rs @@ -0,0 +1,9 @@ +use bpaf::*; + +#[derive(Debug, Clone, Bpaf)] +#[bpaf(options)] +pub struct Options { + /// Message to print in a big friendly letters + #[bpaf(positional("MESSAGE"))] + message: String, +} diff --git a/src/_documentation.rs b/src/_documentation.rs new file mode 100644 index 00000000..67fab18b --- /dev/null +++ b/src/_documentation.rs @@ -0,0 +1,1110 @@ +//! #### Project documentation +//! +//! - [Introduction and design goals](_0_intro) - A quick intro. What, why and how +//! - [Tutorials](_1_tutorials) - practical, learning oriented guides +//! - [HowTo](_2_howto) - Practical solutions to common problems +//! - [Structured API reference](_3_reference) - A better overview of available functions +//! - [Theory explanation](_4_explanation) - Theoretical information about abstractions used by the library, oriented for understanding +//! + pub mod _0_intro { + //! + //! --- + //! + //! + //! [↑ Project documentation](super::super::_documentation) + //! + //! + //! [→ Tutorials](super::_1_tutorials) + //! + //! + //! --- + //! + //! #### Introduction and design goals + //! ##### A quick intro. What, why and how + //! + //! Bpaf is a lightweight and flexible command line parser that uses both combinatoric and derive + //! style API + //! + //! Combinatoric API usually means a bit more typing but no dependency on proc macros and more help + //! from the IDE, derive API uses proc macro to save on typing but your IDE will be less likely to + //! help you. Picking one API style does not lock you out from using the other style, you can mix + //! and match both in a single parser + //! + //! # Examples for both styles + //! + #![cfg_attr(not(doctest), doc = include_str!("docs2/intro.md"))] + //! + //! # Design goals + //! + //! ## Parse, don't validate + //! + //! `bpaf` tries hard to let you to move as much invariants about the user input you are + //! trying to parse into rust type: for mutually exclusive options you can get `enum` with + //! exclusive items going into separate branches, you can collect results into types like + //! [`BTreeSet`](std::collections::BTreeSet), or whatever custom type you might have with + //! custom parsing. Ideas for + //! [making invalid states unrepresentable](https://geeklaunch.io/blog/make-invalid-states-unrepresentable/) + //! and [using parsing over validation](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) + //! are not new. + //! + //! That said you can also validate your inputs if this fits your problem better. If you want to + //! ensure that sum of every numeric fields must be divisible by both 3 and 5, but only when it's + //! Thursday - you can do that too. + //! + //! ## Flexibility + //! + //! While aiming to be a general purpose command line parser `bpaf` offers a few backdoors that + //! allow to parse pretty much anything you want: chained commands, custom blocks of options, DOS + //! style options (`/ofile.pas`), `dd` style options (`if=file of=out`), etc. Similar idea applies + //! for what the parser can produce - your app operates with boxed string slices internally? `bpaf` + //! will give you `Box` instead of `String` if you ask it to. + //! + //! The only restriction being that you cannot use information from items parsed earlier (but not + //! the fact that something was parsed successfully or not) to decide to how to parse further + //! options, and even then you can side step this restrictions by passing some shared state as a + //! parameter to the parsers. + //! + //! ## Reusability + //! + //! Parsers in `bpaf` are not monolithic and you can share their parts across multiple binaries, + //! workspace members or even independent projects. Say you have a multiple binaries in a workspace + //! that perform different operations on some input. You can declare a parser for the input + //! specifically, along with all the validations, help messages or shell dynamic completion + //! functions you need and use it across all the binaries alongside with the arguments specific to + //! those binaries. + //! + //! ## Composition, transformation + //! + //! Parsers in `bpaf` are not finalized either, say you have a parser that describes a single input + //! for your program, it can take multiple arguments or perform extra validations, etc. You can + //! always compose this parser with any other parser to produce tuples of both results for example. + //! Or to make it so parser runs multiple times and collects results into a `Vec`. + //! + //! ## Performance + //! + //! While performance is an explicit non goal - `bpaf` does nothing that would pessimize it either, + //! so performance is on par or better compared to fully featured parsers. + //! + //! ## Correctness + //! + //! `bpaf` would parse only items it can represent and will reject anything it cannot represent + //! in the output. Say your parser accepts both `--intel` and `--att` flags, but encodes the result + //! into `enum Style { Intel, Att }`, `bpaf` will accept those flags separately, but not if they + //! are used both at once. If parser later collects multipe styles into a `Vec