chore(vendor): add anyhow crate
This commit was merged in pull request #68.
This commit is contained in:
83
third_party/crates/clap_builder-4.6.7/src/output/fmt.rs
vendored
Normal file
83
third_party/crates/clap_builder-4.6.7/src/output/fmt.rs
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
use crate::builder::StyledStr;
|
||||
use crate::util::color::ColorChoice;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Stream {
|
||||
Stdout,
|
||||
Stderr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Colorizer {
|
||||
stream: Stream,
|
||||
#[allow(unused)]
|
||||
color_when: ColorChoice,
|
||||
content: StyledStr,
|
||||
}
|
||||
|
||||
impl Colorizer {
|
||||
pub(crate) fn new(stream: Stream, color_when: ColorChoice) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
color_when,
|
||||
content: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_content(mut self, content: StyledStr) -> Self {
|
||||
self.content = content;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Printing methods.
|
||||
impl Colorizer {
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn print(&self) -> std::io::Result<()> {
|
||||
let color_when = match self.color_when {
|
||||
ColorChoice::Always => anstream::ColorChoice::Always,
|
||||
ColorChoice::Auto => anstream::ColorChoice::Auto,
|
||||
ColorChoice::Never => anstream::ColorChoice::Never,
|
||||
};
|
||||
|
||||
let mut stdout;
|
||||
let mut stderr;
|
||||
let writer: &mut dyn std::io::Write = match self.stream {
|
||||
Stream::Stderr => {
|
||||
stderr = anstream::AutoStream::new(std::io::stderr().lock(), color_when);
|
||||
&mut stderr
|
||||
}
|
||||
Stream::Stdout => {
|
||||
stdout = anstream::AutoStream::new(std::io::stdout().lock(), color_when);
|
||||
&mut stdout
|
||||
}
|
||||
};
|
||||
|
||||
self.content.write_to(writer)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn print(&self) -> std::io::Result<()> {
|
||||
// [e]println can't be used here because it panics
|
||||
// if something went wrong. We don't want that.
|
||||
match self.stream {
|
||||
Stream::Stdout => {
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
self.content.write_to(&mut stdout)
|
||||
}
|
||||
Stream::Stderr => {
|
||||
let stderr = std::io::stderr();
|
||||
let mut stderr = stderr.lock();
|
||||
self.content.write_to(&mut stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Color-unaware printing. Never uses coloring.
|
||||
impl std::fmt::Display for Colorizer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.content.fmt(f)
|
||||
}
|
||||
}
|
||||
39
third_party/crates/clap_builder-4.6.7/src/output/help.rs
vendored
Normal file
39
third_party/crates/clap_builder-4.6.7/src/output/help.rs
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
#![cfg_attr(not(feature = "help"), allow(unused_variables))]
|
||||
|
||||
// Internal
|
||||
use crate::builder::Command;
|
||||
use crate::builder::StyledStr;
|
||||
use crate::output::Usage;
|
||||
|
||||
/// Writes the parser help to the wrapped stream.
|
||||
pub(crate) fn write_help(writer: &mut StyledStr, cmd: &Command, usage: &Usage<'_>, use_long: bool) {
|
||||
debug!("write_help");
|
||||
|
||||
if let Some(h) = cmd.get_override_help() {
|
||||
writer.push_styled(h);
|
||||
} else {
|
||||
#[cfg(feature = "help")]
|
||||
{
|
||||
use super::AutoHelp;
|
||||
use super::HelpTemplate;
|
||||
if let Some(tmpl) = cmd.get_help_template() {
|
||||
HelpTemplate::new(writer, cmd, usage, use_long)
|
||||
.write_templated_help(tmpl.as_styled_str());
|
||||
} else {
|
||||
AutoHelp::new(writer, cmd, usage, use_long).write_help();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "help"))]
|
||||
{
|
||||
debug!("write_help: no help, `Command::override_help` and `help` is missing");
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any lines from unused sections
|
||||
writer.trim_start_lines();
|
||||
// Remove any whitespace caused by book keeping
|
||||
writer.trim_end();
|
||||
// Ensure there is still a trailing newline
|
||||
writer.push_str("\n");
|
||||
}
|
||||
1191
third_party/crates/clap_builder-4.6.7/src/output/help_template.rs
vendored
Normal file
1191
third_party/crates/clap_builder-4.6.7/src/output/help_template.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
23
third_party/crates/clap_builder-4.6.7/src/output/mod.rs
vendored
Normal file
23
third_party/crates/clap_builder-4.6.7/src/output/mod.rs
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
mod help;
|
||||
#[cfg(feature = "help")]
|
||||
mod help_template;
|
||||
mod usage;
|
||||
|
||||
pub(crate) mod fmt;
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) mod textwrap;
|
||||
|
||||
pub(crate) use self::help::write_help;
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) use self::help_template::AutoHelp;
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) use self::help_template::HelpTemplate;
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) use self::textwrap::core::display_width;
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) use self::textwrap::wrap;
|
||||
pub(crate) use self::usage::Usage;
|
||||
|
||||
pub(crate) const TAB: &str = " ";
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) const TAB_WIDTH: usize = TAB.len();
|
||||
158
third_party/crates/clap_builder-4.6.7/src/output/textwrap/core.rs
vendored
Normal file
158
third_party/crates/clap_builder-4.6.7/src/output/textwrap/core.rs
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
/// Compute the display width of `text`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// **Note:** When the `unicode` Cargo feature is disabled, all characters are presumed to take up
|
||||
/// 1 width. With the feature enabled, function will correctly deal with [combining characters] in
|
||||
/// their decomposed form (see [Unicode equivalence]).
|
||||
///
|
||||
/// An example of a decomposed character is “é”, which can be decomposed into: “e” followed by a
|
||||
/// combining acute accent: “◌́”. Without the `unicode` Cargo feature, every `char` has a width of
|
||||
/// 1. This includes the combining accent:
|
||||
///
|
||||
/// ## Emojis and CJK Characters
|
||||
///
|
||||
/// Characters such as emojis and [CJK characters] used in the
|
||||
/// Chinese, Japanese, and Korean languages are seen as double-width,
|
||||
/// even if the `unicode-width` feature is disabled:
|
||||
///
|
||||
/// # Limitations
|
||||
///
|
||||
/// The displayed width of a string cannot always be computed from the
|
||||
/// string alone. This is because the width depends on the rendering
|
||||
/// engine used. This is particularly visible with [emoji modifier
|
||||
/// sequences] where a base emoji is modified with, e.g., skin tone or
|
||||
/// hair color modifiers. It is up to the rendering engine to detect
|
||||
/// this and to produce a suitable emoji.
|
||||
///
|
||||
/// A simple example is “❤️”, which consists of “❤” (U+2764: Black
|
||||
/// Heart Symbol) followed by U+FE0F (Variation Selector-16). By
|
||||
/// itself, “❤” is a black heart, but if you follow it with the
|
||||
/// variant selector, you may get a wider red heart.
|
||||
///
|
||||
/// A more complex example would be “👨🦰” which should depict a man
|
||||
/// with red hair. Here the computed width is too large — and the
|
||||
/// width differs depending on the use of the `unicode-width` feature:
|
||||
///
|
||||
/// This happens because the grapheme consists of three code points:
|
||||
/// “👨” (U+1F468: Man), Zero Width Joiner (U+200D), and “🦰”
|
||||
/// (U+1F9B0: Red Hair). You can see them above in the test. With
|
||||
/// `unicode-width` enabled, the ZWJ is correctly seen as having zero
|
||||
/// width, without it is counted as a double-width character.
|
||||
///
|
||||
/// ## Terminal Support
|
||||
///
|
||||
/// Modern browsers typically do a great job at combining characters
|
||||
/// as shown above, but terminals often struggle more. As an example,
|
||||
/// Gnome Terminal version 3.38.1, shows “❤️” as a big red heart, but
|
||||
/// shows "👨🦰" as “👨🦰”.
|
||||
///
|
||||
/// [combining characters]: https://en.wikipedia.org/wiki/Combining_character
|
||||
/// [Unicode equivalence]: https://en.wikipedia.org/wiki/Unicode_equivalence
|
||||
/// [CJK characters]: https://en.wikipedia.org/wiki/CJK_characters
|
||||
/// [emoji modifier sequences]: https://unicode.org/emoji/charts/full-emoji-modifiers.html
|
||||
#[inline(never)]
|
||||
pub(crate) fn display_width(text: &str) -> usize {
|
||||
let mut width = 0;
|
||||
|
||||
let mut control_sequence = false;
|
||||
let control_terminate: char = 'm';
|
||||
|
||||
for ch in text.chars() {
|
||||
if ch.is_ascii_control() {
|
||||
control_sequence = true;
|
||||
} else if control_sequence && ch == control_terminate {
|
||||
control_sequence = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if !control_sequence {
|
||||
width += ch_width(ch);
|
||||
}
|
||||
}
|
||||
width
|
||||
}
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
fn ch_width(ch: char) -> usize {
|
||||
unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unicode"))]
|
||||
fn ch_width(_: char) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
#[test]
|
||||
fn emojis_have_correct_width() {
|
||||
use unic_emoji_char::is_emoji;
|
||||
|
||||
// Emojis in the Basic Latin (ASCII) and Latin-1 Supplement
|
||||
// blocks all have a width of 1 column. This includes
|
||||
// characters such as '#' and '©'.
|
||||
for ch in '\u{1}'..'\u{FF}' {
|
||||
if is_emoji(ch) {
|
||||
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
assert_eq!(ch.width().unwrap(), 1, "char: {desc}");
|
||||
|
||||
#[cfg(not(feature = "unicode"))]
|
||||
assert_eq!(ch_width(ch), 1, "char: {desc}");
|
||||
}
|
||||
}
|
||||
|
||||
// Emojis in the remaining blocks of the Basic Multilingual
|
||||
// Plane (BMP), in the Supplementary Multilingual Plane (SMP),
|
||||
// and in the Supplementary Ideographic Plane (SIP), are all 1
|
||||
// or 2 columns wide when unicode-width is used, and always 2
|
||||
// columns wide otherwise. This includes all of our favorite
|
||||
// emojis such as 😊.
|
||||
for ch in '\u{FF}'..'\u{2FFFF}' {
|
||||
if is_emoji(ch) {
|
||||
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
assert!(ch.width().unwrap() <= 2, "char: {desc}");
|
||||
|
||||
#[cfg(not(feature = "unicode"))]
|
||||
assert_eq!(ch_width(ch), 1, "char: {desc}");
|
||||
}
|
||||
}
|
||||
|
||||
// The remaining planes contain almost no assigned code points
|
||||
// and thus also no emojis.
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn display_width_works() {
|
||||
assert_eq!("Café Plain".len(), 11); // “é” is two bytes
|
||||
assert_eq!(display_width("Café Plain"), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn display_width_narrow_emojis() {
|
||||
assert_eq!(display_width("⁉"), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn display_width_narrow_emojis_variant_selector() {
|
||||
assert_eq!(display_width("⁉\u{fe0f}"), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn display_width_emojis() {
|
||||
assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20);
|
||||
}
|
||||
}
|
||||
122
third_party/crates/clap_builder-4.6.7/src/output/textwrap/mod.rs
vendored
Normal file
122
third_party/crates/clap_builder-4.6.7/src/output/textwrap/mod.rs
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
//! Fork of `textwrap` crate
|
||||
//!
|
||||
//! Benefits of forking:
|
||||
//! - Pull in only what we need rather than relying on the compiler to remove what we don't need
|
||||
//! - `LineWrapper` is able to incrementally wrap which will help with `StyledStr`
|
||||
|
||||
pub(crate) mod core;
|
||||
#[cfg(feature = "wrap_help")]
|
||||
pub(crate) mod word_separators;
|
||||
#[cfg(feature = "wrap_help")]
|
||||
pub(crate) mod wrap_algorithms;
|
||||
|
||||
#[cfg(feature = "wrap_help")]
|
||||
pub(crate) fn wrap(content: &str, hard_width: usize) -> String {
|
||||
let mut wrapper = wrap_algorithms::LineWrapper::new(hard_width);
|
||||
let mut total = Vec::new();
|
||||
for line in content.split_inclusive('\n') {
|
||||
wrapper.reset();
|
||||
let line = word_separators::find_words_ascii_space(line).collect::<Vec<_>>();
|
||||
total.extend(wrapper.wrap(line));
|
||||
}
|
||||
total.join("")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wrap_help"))]
|
||||
pub(crate) fn wrap(content: &str, _hard_width: usize) -> String {
|
||||
content.to_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "wrap_help")]
|
||||
mod test {
|
||||
/// Compatibility shim to keep textwrap's tests
|
||||
fn wrap(content: &str, hard_width: usize) -> Vec<String> {
|
||||
super::wrap(content, hard_width)
|
||||
.trim_end()
|
||||
.split('\n')
|
||||
.map(|s| s.to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_wrap() {
|
||||
assert_eq!(wrap("foo", 10), vec!["foo"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_simple() {
|
||||
assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_be_or_not() {
|
||||
assert_eq!(
|
||||
wrap("To be, or not to be, that is the question.", 10),
|
||||
vec!["To be, or", "not to be,", "that is", "the", "question."]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_words_on_first_line() {
|
||||
assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_word() {
|
||||
assert_eq!(wrap("foo", 0), vec!["foo"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_words() {
|
||||
assert_eq!(wrap("foo bar", 0), vec!["foo", "bar"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_width() {
|
||||
assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]);
|
||||
|
||||
let text = "Hello there! This is some English text. \
|
||||
It should not be wrapped given the extents below.";
|
||||
assert_eq!(wrap(text, usize::MAX), vec![text]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_whitespace() {
|
||||
assert_eq!(wrap(" foo bar", 6), vec![" foo", " bar"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_whitespace_empty_first_line() {
|
||||
// If there is no space for the first word, the first line
|
||||
// will be empty. This is because the string is split into
|
||||
// words like [" ", "foobar ", "baz"], which puts "foobar " on
|
||||
// the second line. We never output trailing whitespace
|
||||
assert_eq!(wrap(" foobar baz", 6), vec![" foobar", " baz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_whitespace() {
|
||||
// Whitespace is only significant inside a line. After a line
|
||||
// gets too long and is broken, the first word starts in
|
||||
// column zero and is not indented.
|
||||
assert_eq!(wrap("foo bar baz ", 5), vec!["foo", "bar", "baz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_99() {
|
||||
// We did not reset the in_whitespace flag correctly and did
|
||||
// not handle single-character words after a line break.
|
||||
assert_eq!(
|
||||
wrap("aaabbbccc x yyyzzzwww", 9),
|
||||
vec!["aaabbbccc", "x", "yyyzzzwww"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_129() {
|
||||
// The dash is an em-dash which takes up four bytes. We used
|
||||
// to panic since we tried to index into the character.
|
||||
assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]);
|
||||
}
|
||||
}
|
||||
92
third_party/crates/clap_builder-4.6.7/src/output/textwrap/word_separators.rs
vendored
Normal file
92
third_party/crates/clap_builder-4.6.7/src/output/textwrap/word_separators.rs
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
pub(crate) fn find_words_ascii_space(line: &str) -> impl Iterator<Item = &'_ str> + '_ {
|
||||
let mut start = 0;
|
||||
let mut in_whitespace = false;
|
||||
let mut char_indices = line.char_indices();
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
for (idx, ch) in char_indices.by_ref() {
|
||||
let next_whitespace = ch == ' ';
|
||||
if in_whitespace && !next_whitespace {
|
||||
let word = &line[start..idx];
|
||||
start = idx;
|
||||
in_whitespace = next_whitespace;
|
||||
return Some(word);
|
||||
}
|
||||
|
||||
in_whitespace = next_whitespace;
|
||||
}
|
||||
|
||||
if start < line.len() {
|
||||
let word = &line[start..];
|
||||
start = line.len();
|
||||
return Some(word);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! test_find_words {
|
||||
($ascii_name:ident,
|
||||
$([ $line:expr, $ascii_words:expr ]),+) => {
|
||||
#[test]
|
||||
fn $ascii_name() {
|
||||
$(
|
||||
let expected_words: Vec<&str> = $ascii_words.to_vec();
|
||||
let actual_words = find_words_ascii_space($line)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual_words, expected_words, "Line: {:?}", $line);
|
||||
)+
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_find_words!(ascii_space_empty, ["", []]);
|
||||
|
||||
test_find_words!(ascii_single_word, ["foo", ["foo"]]);
|
||||
|
||||
test_find_words!(ascii_two_words, ["foo bar", ["foo ", "bar"]]);
|
||||
|
||||
test_find_words!(
|
||||
ascii_multiple_words,
|
||||
["foo bar", ["foo ", "bar"]],
|
||||
["x y z", ["x ", "y ", "z"]]
|
||||
);
|
||||
|
||||
test_find_words!(ascii_only_whitespace, [" ", [" "]], [" ", [" "]]);
|
||||
|
||||
test_find_words!(
|
||||
ascii_inter_word_whitespace,
|
||||
["foo bar", ["foo ", "bar"]]
|
||||
);
|
||||
|
||||
test_find_words!(ascii_trailing_whitespace, ["foo ", ["foo "]]);
|
||||
|
||||
test_find_words!(ascii_leading_whitespace, [" foo", [" ", "foo"]]);
|
||||
|
||||
test_find_words!(
|
||||
ascii_multi_column_char,
|
||||
["\u{1f920}", ["\u{1f920}"]] // cowboy emoji 🤠
|
||||
);
|
||||
|
||||
test_find_words!(
|
||||
ascii_hyphens,
|
||||
["foo-bar", ["foo-bar"]],
|
||||
["foo- bar", ["foo- ", "bar"]],
|
||||
["foo - bar", ["foo ", "- ", "bar"]],
|
||||
["foo -bar", ["foo ", "-bar"]]
|
||||
);
|
||||
|
||||
test_find_words!(ascii_newline, ["foo\nbar", ["foo\nbar"]]);
|
||||
|
||||
test_find_words!(ascii_tab, ["foo\tbar", ["foo\tbar"]]);
|
||||
|
||||
test_find_words!(
|
||||
ascii_non_breaking_space,
|
||||
["foo\u{00A0}bar", ["foo\u{00A0}bar"]]
|
||||
);
|
||||
}
|
||||
68
third_party/crates/clap_builder-4.6.7/src/output/textwrap/wrap_algorithms.rs
vendored
Normal file
68
third_party/crates/clap_builder-4.6.7/src/output/textwrap/wrap_algorithms.rs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
use super::core::display_width;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LineWrapper<'w> {
|
||||
hard_width: usize,
|
||||
line_width: usize,
|
||||
indentation: Option<&'w str>,
|
||||
}
|
||||
|
||||
impl<'w> LineWrapper<'w> {
|
||||
pub(crate) fn new(hard_width: usize) -> Self {
|
||||
Self {
|
||||
hard_width,
|
||||
line_width: 0,
|
||||
indentation: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.line_width = 0;
|
||||
self.indentation = None;
|
||||
}
|
||||
|
||||
pub(crate) fn wrap(&mut self, mut words: Vec<&'w str>) -> Vec<&'w str> {
|
||||
let mut first_word = false;
|
||||
if self.indentation.is_none() {
|
||||
first_word = true;
|
||||
if let Some(word) = words.first() {
|
||||
if word.trim().is_empty() {
|
||||
self.indentation = Some(*word);
|
||||
} else {
|
||||
self.indentation = Some("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
while i < words.len() {
|
||||
let word = &words[i];
|
||||
let trimmed = word.trim_end();
|
||||
let word_width = display_width(trimmed);
|
||||
let trimmed_delta = word.len() - trimmed.len();
|
||||
if first_word && 0 < word_width {
|
||||
// Never try to wrap the first word
|
||||
first_word = false;
|
||||
} else if self.hard_width < self.line_width + word_width {
|
||||
if 0 < i {
|
||||
let prev = i - 1;
|
||||
let trimmed = words[prev].trim_end();
|
||||
words[prev] = trimmed;
|
||||
}
|
||||
|
||||
self.line_width = 0;
|
||||
words.insert(i, "\n");
|
||||
i += 1;
|
||||
if let Some(indentation) = self.indentation {
|
||||
words.insert(i, indentation);
|
||||
self.line_width += indentation.len();
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
self.line_width += word_width + trimmed_delta;
|
||||
|
||||
i += 1;
|
||||
}
|
||||
words
|
||||
}
|
||||
}
|
||||
494
third_party/crates/clap_builder-4.6.7/src/output/usage.rs
vendored
Normal file
494
third_party/crates/clap_builder-4.6.7/src/output/usage.rs
vendored
Normal file
@@ -0,0 +1,494 @@
|
||||
#![cfg_attr(not(feature = "usage"), allow(unused_imports))]
|
||||
#![cfg_attr(not(feature = "usage"), allow(unused_variables))]
|
||||
#![cfg_attr(not(feature = "usage"), allow(clippy::manual_map))]
|
||||
#![cfg_attr(not(feature = "usage"), allow(dead_code))]
|
||||
|
||||
// Internal
|
||||
use crate::builder::ArgAction;
|
||||
use crate::builder::StyledStr;
|
||||
use crate::builder::Styles;
|
||||
use crate::builder::{ArgPredicate, Command};
|
||||
use crate::parser::ArgMatcher;
|
||||
use crate::util::ChildGraph;
|
||||
use crate::util::FlatSet;
|
||||
use crate::util::Id;
|
||||
|
||||
static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND";
|
||||
const USAGE_SEP: &str = "\n ";
|
||||
|
||||
pub(crate) struct Usage<'cmd> {
|
||||
cmd: &'cmd Command,
|
||||
styles: &'cmd Styles,
|
||||
required: Option<&'cmd ChildGraph<Id>>,
|
||||
}
|
||||
|
||||
impl<'cmd> Usage<'cmd> {
|
||||
pub(crate) fn new(cmd: &'cmd Command) -> Self {
|
||||
Usage {
|
||||
cmd,
|
||||
styles: cmd.get_styles(),
|
||||
required: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn required(mut self, required: &'cmd ChildGraph<Id>) -> Self {
|
||||
self.required = Some(required);
|
||||
self
|
||||
}
|
||||
|
||||
// Creates a usage string for display. This happens just after all arguments were parsed, but before
|
||||
// any subcommands have been parsed (so as to give subcommands their own usage recursively)
|
||||
pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr> {
|
||||
debug!("Usage::create_usage_with_title");
|
||||
use std::fmt::Write as _;
|
||||
let mut styled = StyledStr::new();
|
||||
let _ = write!(
|
||||
styled,
|
||||
"{}Usage:{} ",
|
||||
self.styles.get_usage().render(),
|
||||
self.styles.get_usage().render_reset()
|
||||
);
|
||||
if self.write_usage_no_title(&mut styled, used) {
|
||||
styled.trim_end();
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
debug!("Usage::create_usage_with_title: usage={styled}");
|
||||
Some(styled)
|
||||
}
|
||||
|
||||
// Creates a usage string (*without title*) if one was not provided by the user manually.
|
||||
pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr> {
|
||||
debug!("Usage::create_usage_no_title");
|
||||
|
||||
let mut styled = StyledStr::new();
|
||||
if self.write_usage_no_title(&mut styled, used) {
|
||||
styled.trim_end();
|
||||
debug!("Usage::create_usage_no_title: usage={styled}");
|
||||
Some(styled)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a usage string (*without title*) if one was not provided by the user manually.
|
||||
fn write_usage_no_title(&self, styled: &mut StyledStr, used: &[Id]) -> bool {
|
||||
debug!("Usage::create_usage_no_title");
|
||||
if let Some(u) = self.cmd.get_overridden_usage() {
|
||||
styled.push_styled(u);
|
||||
true
|
||||
} else {
|
||||
#[cfg(feature = "usage")]
|
||||
{
|
||||
if used.is_empty() {
|
||||
self.write_help_usage(styled);
|
||||
} else {
|
||||
self.write_smart_usage(styled, used);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "usage"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "usage")]
|
||||
impl Usage<'_> {
|
||||
// Creates a usage string for display in help messages (i.e. not for errors)
|
||||
fn write_help_usage(&self, styled: &mut StyledStr) {
|
||||
debug!("Usage::write_help_usage");
|
||||
use std::fmt::Write;
|
||||
|
||||
if self.cmd.has_visible_subcommands() && self.cmd.is_flatten_help_set() {
|
||||
if !self.cmd.is_subcommand_required_set()
|
||||
|| self.cmd.is_args_conflicts_with_subcommands_set()
|
||||
{
|
||||
self.write_arg_usage(styled, &[], true);
|
||||
styled.trim_end();
|
||||
let _ = write!(styled, "{USAGE_SEP}");
|
||||
}
|
||||
let mut cmd = self.cmd.clone();
|
||||
cmd.build();
|
||||
for (i, sub) in cmd
|
||||
.get_subcommands()
|
||||
.filter(|c| !c.is_hide_set())
|
||||
.enumerate()
|
||||
{
|
||||
if i != 0 {
|
||||
styled.trim_end();
|
||||
let _ = write!(styled, "{USAGE_SEP}");
|
||||
}
|
||||
Usage::new(sub).write_usage_no_title(styled, &[]);
|
||||
}
|
||||
} else {
|
||||
self.write_arg_usage(styled, &[], true);
|
||||
self.write_subcommand_usage(styled);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a context aware usage string, or "smart usage" from currently used
|
||||
// args, and requirements
|
||||
fn write_smart_usage(&self, styled: &mut StyledStr, used: &[Id]) {
|
||||
debug!("Usage::create_smart_usage");
|
||||
use std::fmt::Write;
|
||||
let placeholder = &self.styles.get_placeholder();
|
||||
|
||||
self.write_arg_usage(styled, used, true);
|
||||
|
||||
if self.cmd.is_subcommand_required_set() {
|
||||
let value_name = self
|
||||
.cmd
|
||||
.get_subcommand_value_name()
|
||||
.unwrap_or(DEFAULT_SUB_VALUE_NAME);
|
||||
let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool) {
|
||||
debug!("Usage::write_arg_usage; incl_reqs={incl_reqs:?}");
|
||||
use std::fmt::Write as _;
|
||||
let literal = &self.styles.get_literal();
|
||||
let placeholder = &self.styles.get_placeholder();
|
||||
|
||||
let bin_name = self.cmd.get_usage_name_fallback();
|
||||
if !bin_name.is_empty() {
|
||||
// the trim won't properly remove a leading space due to the formatting
|
||||
let _ = write!(styled, "{literal}{bin_name}{literal:#} ",);
|
||||
}
|
||||
|
||||
if used.is_empty() && self.needs_options_tag() {
|
||||
let _ = write!(styled, "{placeholder}[OPTIONS]{placeholder:#} ",);
|
||||
}
|
||||
|
||||
self.write_args(styled, used, !incl_reqs);
|
||||
}
|
||||
|
||||
fn write_subcommand_usage(&self, styled: &mut StyledStr) {
|
||||
debug!("Usage::write_subcommand_usage");
|
||||
use std::fmt::Write as _;
|
||||
|
||||
// incl_reqs is only false when this function is called recursively
|
||||
if self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set() {
|
||||
let literal = &self.styles.get_literal();
|
||||
let placeholder = &self.styles.get_placeholder();
|
||||
let value_name = self
|
||||
.cmd
|
||||
.get_subcommand_value_name()
|
||||
.unwrap_or(DEFAULT_SUB_VALUE_NAME);
|
||||
if self.cmd.is_subcommand_negates_reqs_set()
|
||||
|| self.cmd.is_args_conflicts_with_subcommands_set()
|
||||
{
|
||||
styled.trim_end();
|
||||
let _ = write!(styled, "{USAGE_SEP}");
|
||||
if self.cmd.is_args_conflicts_with_subcommands_set() {
|
||||
let bin_name = self.cmd.get_usage_name_fallback();
|
||||
// Short-circuit full usage creation since no args will be relevant
|
||||
let _ = write!(styled, "{literal}{bin_name}{literal:#} ",);
|
||||
} else {
|
||||
self.write_arg_usage(styled, &[], false);
|
||||
}
|
||||
let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
|
||||
} else if self.cmd.is_subcommand_required_set() {
|
||||
let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
|
||||
} else {
|
||||
let _ = write!(styled, "{placeholder}[{value_name}]{placeholder:#}",);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determines if we need the `[OPTIONS]` tag in the usage string
|
||||
fn needs_options_tag(&self) -> bool {
|
||||
debug!("Usage::needs_options_tag");
|
||||
'outer: for f in self.cmd.get_non_positionals() {
|
||||
debug!("Usage::needs_options_tag:iter: f={}", f.get_id());
|
||||
|
||||
// Don't print `[OPTIONS]` just for help or version
|
||||
if f.get_long() == Some("help") || f.get_long() == Some("version") {
|
||||
debug!("Usage::needs_options_tag:iter Option is built-in");
|
||||
continue;
|
||||
}
|
||||
match f.get_action() {
|
||||
ArgAction::Set
|
||||
| ArgAction::Append
|
||||
| ArgAction::SetTrue
|
||||
| ArgAction::SetFalse
|
||||
| ArgAction::Count => {}
|
||||
ArgAction::Help
|
||||
| ArgAction::HelpShort
|
||||
| ArgAction::HelpLong
|
||||
| ArgAction::Version => {
|
||||
debug!("Usage::needs_options_tag:iter Option is built-in");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if f.is_hide_set() {
|
||||
debug!("Usage::needs_options_tag:iter Option is hidden");
|
||||
continue;
|
||||
}
|
||||
if f.is_required_set() {
|
||||
debug!("Usage::needs_options_tag:iter Option is required");
|
||||
continue;
|
||||
}
|
||||
for grp_s in self.cmd.groups_for_arg(f.get_id()) {
|
||||
debug!("Usage::needs_options_tag:iter:iter: grp_s={grp_s:?}");
|
||||
if self.cmd.get_groups().any(|g| g.id == grp_s && g.required) {
|
||||
debug!("Usage::needs_options_tag:iter:iter: Group is required");
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Usage::needs_options_tag:iter: [OPTIONS] required");
|
||||
return true;
|
||||
}
|
||||
|
||||
debug!("Usage::needs_options_tag: [OPTIONS] not required");
|
||||
false
|
||||
}
|
||||
|
||||
// Returns the required args in usage string form by fully unrolling all groups
|
||||
pub(crate) fn write_args(&self, styled: &mut StyledStr, incls: &[Id], force_optional: bool) {
|
||||
debug!("Usage::write_args: incls={incls:?}",);
|
||||
use std::fmt::Write as _;
|
||||
let literal = &self.styles.get_literal();
|
||||
|
||||
let required_owned;
|
||||
let required = if let Some(required) = self.required {
|
||||
required
|
||||
} else {
|
||||
required_owned = self.cmd.required_graph();
|
||||
&required_owned
|
||||
};
|
||||
|
||||
let mut unrolled_reqs = Vec::new();
|
||||
for a in required.iter() {
|
||||
let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
|
||||
let required = match val {
|
||||
ArgPredicate::Equals(_) => false,
|
||||
ArgPredicate::IsPresent => true,
|
||||
};
|
||||
required.then(|| req_arg.clone())
|
||||
};
|
||||
|
||||
for aa in self.cmd.unroll_arg_requires(is_relevant, a) {
|
||||
// if we don't check for duplicates here this causes duplicate error messages
|
||||
// see https://github.com/clap-rs/clap/issues/2770
|
||||
unrolled_reqs.push(aa);
|
||||
}
|
||||
// always include the required arg itself. it will not be enumerated
|
||||
// by unroll_requirements_for_arg.
|
||||
unrolled_reqs.push(a.clone());
|
||||
}
|
||||
debug!("Usage::get_args: unrolled_reqs={unrolled_reqs:?}");
|
||||
|
||||
let mut required_groups_members = FlatSet::new();
|
||||
let mut required_groups = FlatSet::new();
|
||||
for req in unrolled_reqs.iter().chain(incls.iter()) {
|
||||
if self.cmd.find_group(req).is_some() {
|
||||
let group_members = self.cmd.unroll_args_in_group(req);
|
||||
let elem = self.cmd.format_group(req);
|
||||
required_groups.insert(elem);
|
||||
required_groups_members.extend(group_members);
|
||||
} else {
|
||||
debug_assert!(self.cmd.find(req).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
let mut required_opts = FlatSet::new();
|
||||
let mut required_positionals = Vec::new();
|
||||
for req in unrolled_reqs.iter().chain(incls.iter()) {
|
||||
if let Some(arg) = self.cmd.find(req) {
|
||||
if required_groups_members.contains(arg.get_id()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let stylized = arg.stylized(self.styles, Some(!force_optional));
|
||||
if let Some(index) = arg.get_index() {
|
||||
let new_len = index + 1;
|
||||
if required_positionals.len() < new_len {
|
||||
required_positionals.resize(new_len, None);
|
||||
}
|
||||
required_positionals[index] = Some(stylized);
|
||||
} else {
|
||||
required_opts.insert(stylized);
|
||||
}
|
||||
} else {
|
||||
debug_assert!(self.cmd.find_group(req).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
for pos in self.cmd.get_positionals() {
|
||||
if pos.is_hide_set() {
|
||||
continue;
|
||||
}
|
||||
if required_groups_members.contains(pos.get_id()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let index = pos.get_index().unwrap();
|
||||
let new_len = index + 1;
|
||||
if required_positionals.len() < new_len {
|
||||
required_positionals.resize(new_len, None);
|
||||
}
|
||||
if required_positionals[index].is_some() {
|
||||
if pos.is_last_set() {
|
||||
let styled = required_positionals[index].take().unwrap();
|
||||
let mut new = StyledStr::new();
|
||||
let _ = write!(new, "{literal}--{literal:#} ");
|
||||
new.push_styled(&styled);
|
||||
required_positionals[index] = Some(new);
|
||||
}
|
||||
} else {
|
||||
let mut styled;
|
||||
if pos.is_last_set() {
|
||||
styled = StyledStr::new();
|
||||
let _ = write!(styled, "{literal}[--{literal:#} ");
|
||||
styled.push_styled(&pos.stylized(self.styles, Some(true)));
|
||||
let _ = write!(styled, "{literal}]{literal:#}");
|
||||
} else {
|
||||
styled = pos.stylized(self.styles, Some(false));
|
||||
}
|
||||
required_positionals[index] = Some(styled);
|
||||
}
|
||||
if pos.is_last_set() && force_optional {
|
||||
required_positionals[index] = None;
|
||||
}
|
||||
}
|
||||
|
||||
if !force_optional {
|
||||
for arg in required_opts {
|
||||
styled.push_styled(&arg);
|
||||
styled.push_str(" ");
|
||||
}
|
||||
for arg in required_groups {
|
||||
styled.push_styled(&arg);
|
||||
styled.push_str(" ");
|
||||
}
|
||||
}
|
||||
for arg in required_positionals.into_iter().flatten() {
|
||||
styled.push_styled(&arg);
|
||||
styled.push_str(" ");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_required_usage_from(
|
||||
&self,
|
||||
incls: &[Id],
|
||||
matcher: Option<&ArgMatcher>,
|
||||
incl_last: bool,
|
||||
) -> Vec<StyledStr> {
|
||||
debug!(
|
||||
"Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}",
|
||||
incls,
|
||||
matcher.is_some(),
|
||||
incl_last
|
||||
);
|
||||
|
||||
let required_owned;
|
||||
let required = if let Some(required) = self.required {
|
||||
required
|
||||
} else {
|
||||
required_owned = self.cmd.required_graph();
|
||||
&required_owned
|
||||
};
|
||||
|
||||
let mut unrolled_reqs = Vec::new();
|
||||
for a in required.iter() {
|
||||
let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
|
||||
let required = match val {
|
||||
ArgPredicate::Equals(_) => {
|
||||
if let Some(matcher) = matcher {
|
||||
matcher.check_explicit(a, val)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
ArgPredicate::IsPresent => true,
|
||||
};
|
||||
required.then(|| req_arg.clone())
|
||||
};
|
||||
|
||||
for aa in self.cmd.unroll_arg_requires(is_relevant, a) {
|
||||
// if we don't check for duplicates here this causes duplicate error messages
|
||||
// see https://github.com/clap-rs/clap/issues/2770
|
||||
unrolled_reqs.push(aa);
|
||||
}
|
||||
// always include the required arg itself. it will not be enumerated
|
||||
// by unroll_requirements_for_arg.
|
||||
unrolled_reqs.push(a.clone());
|
||||
}
|
||||
debug!("Usage::get_required_usage_from: unrolled_reqs={unrolled_reqs:?}");
|
||||
|
||||
let mut required_groups_members = FlatSet::new();
|
||||
let mut required_groups = FlatSet::new();
|
||||
for req in unrolled_reqs.iter().chain(incls.iter()) {
|
||||
if self.cmd.find_group(req).is_some() {
|
||||
let group_members = self.cmd.unroll_args_in_group(req);
|
||||
let is_present = matcher
|
||||
.map(|m| {
|
||||
group_members
|
||||
.iter()
|
||||
.any(|arg| m.check_explicit(arg, &ArgPredicate::IsPresent))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
debug!("Usage::get_required_usage_from:iter:{req:?} group is_present={is_present}");
|
||||
if is_present {
|
||||
continue;
|
||||
}
|
||||
|
||||
let elem = self.cmd.format_group(req);
|
||||
required_groups.insert(elem);
|
||||
required_groups_members.extend(group_members);
|
||||
} else {
|
||||
debug_assert!(self.cmd.find(req).is_some(), "`{req}` must exist");
|
||||
}
|
||||
}
|
||||
|
||||
let mut required_opts = FlatSet::new();
|
||||
let mut required_positionals = Vec::new();
|
||||
for req in unrolled_reqs.iter().chain(incls.iter()) {
|
||||
if let Some(arg) = self.cmd.find(req) {
|
||||
if required_groups_members.contains(arg.get_id()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_present = matcher
|
||||
.map(|m| m.check_explicit(req, &ArgPredicate::IsPresent))
|
||||
.unwrap_or(false);
|
||||
debug!("Usage::get_required_usage_from:iter:{req:?} arg is_present={is_present}");
|
||||
if is_present {
|
||||
continue;
|
||||
}
|
||||
|
||||
let stylized = arg.stylized(self.styles, Some(true));
|
||||
if let Some(index) = arg.get_index() {
|
||||
if !arg.is_last_set() || incl_last {
|
||||
let new_len = index + 1;
|
||||
if required_positionals.len() < new_len {
|
||||
required_positionals.resize(new_len, None);
|
||||
}
|
||||
required_positionals[index] = Some(stylized);
|
||||
}
|
||||
} else {
|
||||
required_opts.insert(stylized);
|
||||
}
|
||||
} else {
|
||||
debug_assert!(self.cmd.find_group(req).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
let mut ret_val = Vec::new();
|
||||
ret_val.extend(required_opts);
|
||||
ret_val.extend(required_groups);
|
||||
for pos in required_positionals.into_iter().flatten() {
|
||||
ret_val.push(pos);
|
||||
}
|
||||
|
||||
debug!("Usage::get_required_usage_from: ret_val={ret_val:?}");
|
||||
ret_val
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user