mirror of
https://github.com/gay-pizza/jaarg.git
synced 2025-12-18 23:10:17 +00:00
Add with chain for excluding options from showing up in help
This commit is contained in:
@@ -59,11 +59,11 @@ println!("{file:?} -> {out:?} (number: {number:?})",
|
||||
|
||||
main:
|
||||
* Moved `Opts::parse_map` into newly introduced `alloc` crate, making it accessible for `no_std` users.
|
||||
* API updates, enough public constructs to roll a custom help writer.
|
||||
* Generalised internal error & usage into `StandardErrorUsageWriter` for reuse outside the easy API & in `no_std`.
|
||||
* Fixed forced newline in user display in easy API.
|
||||
* More generic & flexible help API: removed forced newline, moved error writer to `StandardErrorUsageWriter`,
|
||||
generalised "Usage" line in standard full writer, enough public constructs to roll a custom help writer.
|
||||
* Added the ability to exclude options from short usage, full help, or both.
|
||||
* More tests for validating internal behaviour & enabled CI on GitHub.
|
||||
* New `no_std` examples.
|
||||
* Added new `no_std` examples.
|
||||
|
||||
v0.1.1:
|
||||
* Fixed incorrect error message format for coerced parsing errors.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
use jaarg::{Opt, Opts, ParseControl, ParseResult};
|
||||
use jaarg::{Opt, OptHide, Opts, ParseControl, ParseResult};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
@@ -15,7 +15,8 @@ fn main() {
|
||||
// Set up arguments table
|
||||
enum Arg { Help, Number, File, Out }
|
||||
const OPTIONS: Opts<Arg> = Opts::new(&[
|
||||
Opt::help_flag(Arg::Help, &["-h", "--help"]).help_text("Show this help and exit."),
|
||||
Opt::help_flag(Arg::Help, &["-h", "--help"]).hide_usage(OptHide::Short)
|
||||
.help_text("Show this help and exit."),
|
||||
Opt::value(Arg::Number, &["-n", "--number"], "value")
|
||||
.help_text("Optionally specify a number (default: 0)"),
|
||||
Opt::positional(Arg::File, "file").required()
|
||||
|
||||
@@ -31,7 +31,7 @@ impl<ID: 'static> core::fmt::Display for StandardShortUsageWriter<'_, ID> {
|
||||
|
||||
// Write option parameter arguments
|
||||
for option in self.0.options.iter()
|
||||
.filter(|o| matches!(o.r#type, OptType::Value | OptType::Flag)) {
|
||||
.filter(|o| matches!((o.r#type, o.is_short_visible()), (OptType::Value | OptType::Flag, true))) {
|
||||
write!(f, " {}", if option.is_required() { '<' } else { '[' })?;
|
||||
match (option.first_short_name(), option.first_long_name()) {
|
||||
(Some(short_name), Some(long_name)) => write!(f, "{short_name}|{long_name}")?,
|
||||
@@ -47,7 +47,7 @@ impl<ID: 'static> core::fmt::Display for StandardShortUsageWriter<'_, ID> {
|
||||
|
||||
// Write positional arguments
|
||||
for option in self.0.options.iter()
|
||||
.filter(|o| matches!(o.r#type, OptType::Positional)) {
|
||||
.filter(|o| matches!((o.r#type, o.is_short_visible()), (OptType::Positional, true))) {
|
||||
let name = option.first_name();
|
||||
match option.is_required() {
|
||||
true => write!(f, " <{name}>")?,
|
||||
@@ -66,8 +66,6 @@ impl<'a, ID: 'static> HelpWriter<'a, ID> for StandardFullHelpWriter<'a, ID> {
|
||||
|
||||
impl<ID> core::fmt::Display for StandardFullHelpWriter<'_, ID> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
use core::fmt::Write;
|
||||
|
||||
// Base short usage
|
||||
writeln!(f, "{}", StandardShortUsageWriter::new(self.0.clone()))?;
|
||||
|
||||
@@ -91,7 +89,7 @@ impl<ID> core::fmt::Display for StandardFullHelpWriter<'_, ID> {
|
||||
// Write positional argument descriptions
|
||||
let mut first = true;
|
||||
for option in self.0.options.iter()
|
||||
.filter(|o| matches!(o.r#type, OptType::Positional)) {
|
||||
.filter(|o| matches!((o.r#type, o.is_full_visible()), (OptType::Positional, true))) {
|
||||
if first {
|
||||
// Write separator and positional section header
|
||||
writeln!(f)?;
|
||||
@@ -111,7 +109,7 @@ impl<ID> core::fmt::Display for StandardFullHelpWriter<'_, ID> {
|
||||
// Write option parameter argument descriptions
|
||||
first = true;
|
||||
for option in self.0.options.iter()
|
||||
.filter(|o| matches!(o.r#type, OptType::Flag | OptType::Value)) {
|
||||
.filter(|o| matches!((o.r#type, o.is_full_visible()), (OptType::Flag | OptType::Value, true))) {
|
||||
if first {
|
||||
// Write separator and options section header
|
||||
writeln!(f)?;
|
||||
|
||||
@@ -27,14 +27,24 @@ pub struct Opt<ID> {
|
||||
flags: OptFlag,
|
||||
}
|
||||
|
||||
pub enum OptHide {
|
||||
Short,
|
||||
Full,
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct OptFlag(u8);
|
||||
|
||||
impl OptFlag {
|
||||
pub const REQUIRED: Self = OptFlag(1 << 0);
|
||||
pub const HELP: Self = OptFlag(1 << 1);
|
||||
#[allow(dead_code)]
|
||||
pub const NONE: Self = Self(0);
|
||||
pub const REQUIRED: Self = OptFlag(1 << 0);
|
||||
pub const HELP: Self = OptFlag(1 << 1);
|
||||
pub const VISIBLE_SHORT: Self = OptFlag(1 << 2);
|
||||
pub const VISIBLE_FULL: Self = OptFlag(1 << 3);
|
||||
|
||||
pub const NONE: Self = OptFlag(0);
|
||||
pub const DEFAULT: Self = Self(Self::VISIBLE_SHORT.0 | Self::VISIBLE_FULL.0);
|
||||
}
|
||||
|
||||
// TODO: Improve this interface by making the name field take AsOptIdentifier when const traits are stabilised
|
||||
@@ -45,7 +55,7 @@ impl<ID> Opt<ID> {
|
||||
OptIdentifier::Single(_) => true,
|
||||
OptIdentifier::Multi(names) => !names.is_empty(),
|
||||
}, "Option names cannot be an empty slice");
|
||||
Self { id, names, value_name, help_string: None, r#type, flags: OptFlag::NONE }
|
||||
Self { id, names, value_name, help_string: None, r#type, flags: OptFlag::DEFAULT }
|
||||
}
|
||||
|
||||
/// A positional argument that is parsed sequentially without being invoked by an option flag.
|
||||
@@ -81,6 +91,17 @@ impl<ID> Opt<ID> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Marks the option to exclude it from appearing in short usage text, full help text, or both.
|
||||
#[inline]
|
||||
pub const fn hide_usage(mut self, from: OptHide) -> Self {
|
||||
self.flags.0 &= !match from {
|
||||
OptHide::Short => OptFlag::VISIBLE_SHORT.0,
|
||||
OptHide::Full => OptFlag::VISIBLE_FULL.0,
|
||||
OptHide::All => OptFlag::VISIBLE_SHORT.0 | OptFlag::VISIBLE_FULL.0,
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn with_help_flag(mut self) -> Self {
|
||||
assert!(matches!(self.r#type, OptType::Flag), "Only flags are allowed to be help options");
|
||||
@@ -89,14 +110,26 @@ impl<ID> Opt<ID> {
|
||||
}
|
||||
|
||||
/// Returns true if this is a required positional argument, or required option argument.
|
||||
#[inline(always)] pub const fn is_required(&self) -> bool {
|
||||
#[inline(always)]
|
||||
pub const fn is_required(&self) -> bool {
|
||||
(self.flags.0 & OptFlag::REQUIRED.0) != 0
|
||||
}
|
||||
|
||||
/// Returns true if this is the help option.
|
||||
#[inline(always)] pub const fn is_help(&self) -> bool {
|
||||
#[inline(always)]
|
||||
pub const fn is_help(&self) -> bool {
|
||||
(self.flags.0 & OptFlag::HELP.0) != 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn is_short_visible(&self) -> bool {
|
||||
(self.flags.0 & OptFlag::VISIBLE_SHORT.0) != 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn is_full_visible(&self) -> bool {
|
||||
(self.flags.0 & OptFlag::VISIBLE_FULL.0) != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<ID: 'static> Opt<ID> {
|
||||
@@ -199,6 +232,10 @@ impl<ID: 'static> Opt<ID> {
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitOr for OptFlag {
|
||||
type Output = Self;
|
||||
fn bitor(self, rhs: Self) -> Self::Output { Self(self.0 | rhs.0) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod opt_tests {
|
||||
@@ -214,19 +251,19 @@ mod opt_tests {
|
||||
fn test_public_initialisers() {
|
||||
assert_eq!(Opt::positional((), "name"), Opt { id: (),
|
||||
names: OptIdentifier::Single("name"), value_name: None, help_string: None,
|
||||
r#type: OptType::Positional, flags: OptFlag::NONE,
|
||||
r#type: OptType::Positional, flags: OptFlag::DEFAULT,
|
||||
});
|
||||
assert_eq!(Opt::help_flag((), &["name"]), Opt { id: (),
|
||||
names: OptIdentifier::Multi(&["name"]), value_name: None, help_string: None,
|
||||
r#type: OptType::Flag, flags: OptFlag::HELP,
|
||||
r#type: OptType::Flag, flags: OptFlag::DEFAULT | OptFlag::HELP,
|
||||
});
|
||||
assert_eq!(Opt::flag((), &["name"]), Opt { id: (),
|
||||
names: OptIdentifier::Multi(&["name"]), value_name: None, help_string: None,
|
||||
r#type: OptType::Flag, flags: OptFlag::NONE,
|
||||
r#type: OptType::Flag, flags: OptFlag::DEFAULT,
|
||||
});
|
||||
assert_eq!(Opt::value((), &["name"], "value"), Opt { id: (),
|
||||
names: OptIdentifier::Multi(&["name"]), value_name: Some("value"), help_string: None,
|
||||
r#type: OptType::Value, flags: OptFlag::NONE,
|
||||
r#type: OptType::Value, flags: OptFlag::DEFAULT,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,16 +271,32 @@ mod opt_tests {
|
||||
fn test_valid_with_chains() {
|
||||
assert_eq!(Opt::positional((), "").required(), Opt { id: (),
|
||||
names: OptIdentifier::Single(""), value_name: None, help_string: None,
|
||||
r#type: OptType::Positional, flags: OptFlag::REQUIRED,
|
||||
r#type: OptType::Positional, flags: OptFlag::DEFAULT | OptFlag::REQUIRED,
|
||||
});
|
||||
assert_eq!(Opt::positional((), "").required().help_text("help string"), Opt { id: (),
|
||||
names: OptIdentifier::Single(""), value_name: None, help_string: Some("help string"),
|
||||
r#type: OptType::Positional, flags: OptFlag::REQUIRED,
|
||||
r#type: OptType::Positional, flags: OptFlag::DEFAULT | OptFlag::REQUIRED,
|
||||
});
|
||||
assert_eq!(Opt::positional((), "").help_text("help string"), Opt { id: (),
|
||||
names: OptIdentifier::Single(""), value_name: None, help_string: Some("help string"),
|
||||
r#type: OptType::Positional, flags: OptFlag::DEFAULT,
|
||||
});
|
||||
assert_eq!(Opt::positional((), "").hide_usage(OptHide::Short), Opt { id: (),
|
||||
names: OptIdentifier::Single(""), value_name: None, help_string: None,
|
||||
r#type: OptType::Positional, flags: OptFlag::VISIBLE_FULL,
|
||||
});
|
||||
assert_eq!(Opt::positional((), "").hide_usage(OptHide::Full), Opt { id: (),
|
||||
names: OptIdentifier::Single(""), value_name: None, help_string: None,
|
||||
r#type: OptType::Positional, flags: OptFlag::VISIBLE_SHORT,
|
||||
});
|
||||
assert_eq!(Opt::positional((), "").hide_usage(OptHide::All), Opt { id: (),
|
||||
names: OptIdentifier::Single(""), value_name: None, help_string: None,
|
||||
r#type: OptType::Positional, flags: OptFlag::NONE,
|
||||
});
|
||||
assert_eq!(Opt::positional((), "").required().hide_usage(OptHide::All), Opt { id: (),
|
||||
names: OptIdentifier::Single(""), value_name: None, help_string: None,
|
||||
r#type: OptType::Positional, flags: OptFlag::REQUIRED,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user