#![cfg_attr(feature = "derive", doc = "```")]
#![cfg_attr(not(feature = "derive"), doc = "```ignore")]
#![cfg_attr(
feature = "derive",
doc = "- [`derive@Args`], [`derive@Subcommand`], [`derive@ValueEnum`] and [`derive@Occurrence`]:
the derives. Each page lists every attribute it takes."
)]
pub mod color;
pub mod combinator;
pub mod complete;
pub mod env;
pub mod error;
pub mod help;
pub mod response;
pub mod run;
pub mod stream;
pub mod token;
pub mod value;
pub use env::with_env;
pub use error::{Error, ErrorKind};
pub use stream::{Argv, words};
pub use token::Arg;
pub use value::{ChoiceError, FromArg};
pub use winnow::stream::BStr;
#[cfg(feature = "derive")]
pub use winnow_args_derive::{Args, Occurrence, Subcommand, ValueEnum};
#[cfg(all(doctest, feature = "derive"))]
#[doc = include_str!("../README.md")]
pub struct Readme;
#[cfg(all(doctest, feature = "derive"))]
pub mod derive_examples {
}
pub trait Args: Sized {
#[doc(hidden)]
const SPEC: &'static str = "";
fn parse_argv(input: &mut Argv<'_>) -> Result<Self, Error> {
Self::parse_argv_with(input, &mut ())
}
fn parse_argv_with(input: &mut Argv<'_>, globals: &mut dyn Globals) -> Result<Self, Error>;
#[doc(hidden)]
fn help_data() -> &'static help::Command {
Self::HELP
}
const HELP: &'static help::Command = &help::Command {
name: "",
about: "",
long_about: "",
after_help: "",
after_long_help: "",
items: &[],
subcommands: &[],
subcommand_required: false,
help_flag: false,
help_short: false,
version: None,
};
fn completion_request(args: &[std::ffi::OsString]) -> Option<String> {
complete::answer(Self::help_data(), args)
}
fn completion_script(shell: complete::Shell) -> String {
complete::script(Self::HELP.name, shell)
}
fn parse_words(words: &[&BStr]) -> Result<Self, Error> {
Self::parse_argv(&mut Argv::new(words)).map_err(|error| error.suggested(Self::help_data()))
}
fn parse_from_argv<I, S>(argv: I) -> Result<Self, Error>
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
Self::parse_from(argv.into_iter().skip(1))
}
fn parse_from<I, S>(args: I) -> Result<Self, Error>
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
let args: Vec<S> = args.into_iter().collect();
Self::parse_words(&words(&args))
}
fn parse() -> Self {
match Self::try_parse() {
Ok(parsed) => parsed,
Err(error) => std::process::exit(report(&error, &Self::program())),
}
}
fn try_parse() -> Result<Self, Error> {
Self::parse_from_argv(std::env::args_os())
}
fn program() -> String {
if !Self::HELP.name.is_empty() {
return Self::HELP.name.to_owned();
}
let argv0 = std::env::args_os().next().unwrap_or_default();
std::path::Path::new(&argv0)
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_default()
}
}
pub fn report(error: &Error, program: &str) -> i32 {
report_with(error, program, &color::Theme::DEFAULT)
}
pub fn report_with(error: &Error, program: &str, theme: &color::Theme) -> i32 {
use std::io::IsTerminal as _;
let stdout = || help::Style::themed(theme, std::io::stdout().is_terminal());
let stderr = || help::Style::themed(theme, std::io::stderr().is_terminal());
let bare = error.is_bare_help();
let style = if bare { stderr() } else { stdout() };
if let Some(help) = error.render_help_styled(program, style) {
if bare {
eprintln!("{help}");
return 2;
}
println!("{help}");
return 0;
}
if let Some(version) = error.version_text(program) {
println!("{version}");
return 0;
}
eprintln!("{}", error.render(stderr()));
2
}
pub trait Occurrence: Sized {
#[doc(hidden)]
const SPEC: &'static str = "";
const PREFIXES: &'static [u8] = &[];
fn from_arg<'i>(arg: &Arg<'i>, input: &mut Argv<'i>) -> Result<Option<Self>, Error>;
fn from_word(word: &token::Word<'_>) -> Result<Option<Self>, Error>;
fn is_long(name: &[u8]) -> bool;
const ITEMS: &'static [help::Item] = &[];
const POSITIONAL: bool = false;
const BUNDLES: bool = false;
fn from_bundle(word: &token::Word<'_>) -> Result<Option<Self>, Error> {
let _ = word;
Ok(None)
}
fn from_unknown(word: &token::Word<'_>) -> Result<Option<Self>, Error> {
let _ = word;
Ok(None)
}
fn short(letter: char) -> Option<bool> {
let _ = letter;
None
}
}
pub trait Subcommand: Sized {
#[doc(hidden)]
const SPEC: &'static str = "";
fn has(name: &[u8]) -> bool;
fn parse_subcommand(
name: &[u8],
input: &mut Argv<'_>,
globals: &mut dyn Globals,
) -> Result<Self, Error>;
}
pub trait Globals {
fn bind<'i>(&mut self, arg: &Arg<'i>, input: &mut Argv<'i>) -> Result<bool, Error>;
fn short(&self, letter: char) -> Option<bool> {
let _ = letter;
None
}
}
impl Globals for () {
#[inline]
fn bind<'i>(&mut self, _: &Arg<'i>, _: &mut Argv<'i>) -> Result<bool, Error> {
Ok(false)
}
}
pub fn globals<F>(bind: F) -> impl Globals
where
F: for<'i> FnMut(&Arg<'i>, &mut Argv<'i>) -> Result<bool, Error>,
{
struct FromFn<F>(F);
impl<F> Globals for FromFn<F>
where
F: for<'i> FnMut(&Arg<'i>, &mut Argv<'i>) -> Result<bool, Error>,
{
#[inline]
fn bind<'i>(&mut self, arg: &Arg<'i>, input: &mut Argv<'i>) -> Result<bool, Error> {
(self.0)(arg, input)
}
}
FromFn(bind)
}
#[doc(hidden)]
pub fn inherit<'a, F>(
shorts: &'static [(char, bool)],
flattened: fn(char) -> Option<bool>,
parent: &'a mut dyn Globals,
bind: F,
) -> impl Globals + 'a
where
F: for<'i> FnMut(&Arg<'i>, &mut Argv<'i>, &mut dyn Globals) -> Result<bool, Error> + 'a,
{
struct Inherit<'a, F> {
shorts: &'static [(char, bool)],
flattened: fn(char) -> Option<bool>,
parent: &'a mut dyn Globals,
bind: F,
}
impl<F> Globals for Inherit<'_, F>
where
F: for<'i> FnMut(&Arg<'i>, &mut Argv<'i>, &mut dyn Globals) -> Result<bool, Error>,
{
#[inline]
fn bind<'i>(&mut self, arg: &Arg<'i>, input: &mut Argv<'i>) -> Result<bool, Error> {
(self.bind)(arg, input, &mut *self.parent)
}
fn short(&self, letter: char) -> Option<bool> {
match self.shorts.iter().find(|(c, _)| *c == letter) {
Some(&(_, takes_value)) => Some(takes_value),
None => (self.flattened)(letter).or_else(|| self.parent.short(letter)),
}
}
}
Inherit {
shorts,
flattened,
parent,
bind,
}
}
#[cfg(feature = "help-text")]
#[doc(hidden)]
#[macro_export]
macro_rules! __text {
($text:literal) => {
$text
};
}
#[cfg(not(feature = "help-text"))]
#[doc(hidden)]
#[macro_export]
macro_rules! __text {
($text:literal) => {
""
};
}
#[doc(hidden)]
pub mod __private {
pub use crate::env;
pub use crate::error::Error;
pub use crate::help::concat_items;
pub use crate::stream::Argv;
pub use crate::token::{Arg, ValueOptions, Word, arg, arg_plus, long_only, number, split};
pub use crate::value::{ChoiceError, FromArg, documented_choices, filed_as, stated_choices};
pub use crate::{Globals, Subcommand, globals, inherit};
pub use winnow::stream::BStr;
pub type BoxError = crate::error::BoxError;
use crate::token::is_flag_like;
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be flattened",
note = "a flattened struct derives `Args` and has only flags: no positionals, \
subcommand, `sequence`, `unknown`, `keywords`, \
`unknown_flags = \"value\"` or generics"
)]
pub trait Flatten: Sized {
type Slots: Default;
fn bind<'i>(
slots: &mut Self::Slots,
arg: &Arg<'i>,
input: &mut Argv<'i>,
) -> Result<bool, Error>;
fn short(letter: char) -> Option<bool>;
fn bind_global<'i>(
slots: &mut Self::Slots,
arg: &Arg<'i>,
input: &mut Argv<'i>,
) -> Result<bool, Error>;
fn global_short(letter: char) -> Option<bool>;
fn is_long(name: &[u8]) -> bool;
fn is_prefix(letter: u8) -> bool;
fn finish(slots: Self::Slots, input: &Argv<'_>) -> Result<Self, Error>;
}
pub fn help_word(root: &'static crate::help::Command, input: &mut Argv<'_>) -> Error {
let mut words = Vec::new();
while !input.is_empty() && !is_flag_like(input.front()) {
words.push(input.take_word());
}
let (command, path) = crate::help::resolve(root, words.iter().map(|w| &***w));
path.into_iter()
.rev()
.fold(Error::help(command, true), Error::within)
}
pub fn bundle_known(token: &[u8], known: impl Fn(char) -> Option<bool>) -> bool {
let Some(mut rest) = token.strip_prefix(b"-") else {
return false;
};
while let Some((&byte, tail)) = rest.split_first() {
if !byte.is_ascii() {
let Ok(letters) = core::str::from_utf8(rest) else {
return false;
};
return letters.chars().try_for_each(|letter| match known(letter) {
None => Err(false),
Some(true) => Err(true),
Some(false) => Ok(()),
}) != Err(false);
}
match known(byte as char) {
None => return false,
Some(true) => return true,
Some(false) => rest = tail,
}
}
true
}
#[inline]
pub fn unknown_bundle<'i>(
input: &mut Argv<'i>,
known: impl Fn(char) -> Option<bool>,
) -> Option<Word<'i>> {
let front = input.front();
let short = input.mode() == crate::stream::Mode::Word
&& front.len() > 2
&& front[0] == b'-'
&& front[1] != b'-';
if !short || bundle_known(front, known) {
return None;
}
let offset = input.offset();
Some(Word {
value: input.take_word(),
offset,
after_separator: false,
})
}
pub fn keywords<T: crate::Args>(values: &[&BStr], flag: &str, at: usize) -> Result<T, Error> {
let spelled: Vec<Vec<u8>> = values
.iter()
.map(|v| [b"--".as_slice(), v].concat())
.collect();
let words: Vec<&BStr> = spelled.iter().map(|w| BStr::new(w.as_slice())).collect();
T::parse_words(&words).map_err(|e| {
let keyword = e.token().map_or_else(String::new, |t| {
t.strip_prefix("--").unwrap_or(t).to_owned()
});
Error::invalid_value(at, flag, keyword.as_bytes(), e)
})
}
pub fn finish_with(
input: &mut Argv<'_>,
globals: &mut dyn Globals,
help: &'static crate::help::Command,
) -> Result<(), Error> {
while !input.is_empty() {
let arg = arg(input)?;
match arg {
Arg::Separator { .. } => {}
Arg::Long(_) | Arg::Short(_) if globals.bind(&arg, input)? => {}
Arg::Long(flag) if flag.name == b"help" => return Err(Error::help(help, true)),
Arg::Short(flag) if flag.letter == 'h' => return Err(Error::help(help, false)),
_ => return Err(arg.unexpected()),
}
}
Ok(())
}
}