mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 14:54:15 +00:00
Merge pull request #4765 from epage/anstream
feat(help): Allow user-provided styled text in `StyledStr`
This commit is contained in:
commit
52eab28f1d
11 changed files with 195 additions and 271 deletions
50
Cargo.lock
generated
50
Cargo.lock
generated
|
@ -25,9 +25,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
|||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.2.3"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd0982309face56a044e935a18bbffcddeb1ce72e69a3ecc3bafb56d4e959f37"
|
||||
checksum = "2abc612600451a4beeff27bf046474b29f7eab30b15846975949f30f9e54afad"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
|
@ -40,9 +40,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "0.3.3"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453bc2a7b261f8c4d1ce5b2c6c222d648d00988d30315e4911fbddc4ddf8983c"
|
||||
checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
|
@ -203,18 +203,19 @@ dependencies = [
|
|||
name = "clap_builder"
|
||||
version = "4.1.14"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"backtrace",
|
||||
"bitflags",
|
||||
"clap_lex 0.4.0",
|
||||
"color-print",
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"shlex",
|
||||
"snapbox",
|
||||
"static_assertions",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"terminal_size",
|
||||
"trybuild",
|
||||
"trycmd",
|
||||
|
@ -278,6 +279,27 @@ dependencies = [
|
|||
"snapbox",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-print"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2a5e6504ed8648554968650feecea00557a3476bc040d0ffc33080e66b646d0"
|
||||
dependencies = [
|
||||
"color-print-proc-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-print-proc-macro"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concolor-override"
|
||||
version = "1.0.0"
|
||||
|
@ -579,6 +601,12 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
|
@ -588,6 +616,16 @@ dependencies = [
|
|||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normalize-line-endings"
|
||||
version = "0.3.0"
|
||||
|
|
|
@ -30,20 +30,13 @@ dependent-version = "upgrade"
|
|||
tag-name = "v{{version}}"
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"std",
|
||||
"color",
|
||||
"help",
|
||||
"usage",
|
||||
"error-context",
|
||||
"suggestions",
|
||||
]
|
||||
default = ["std", "color", "help", "usage", "error-context", "suggestions"]
|
||||
debug = ["dep:backtrace"] # Enables debug messages
|
||||
unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string", "unstable-replace"] # for docs.rs
|
||||
|
||||
# Used in default
|
||||
std = [] # support for no_std in a backwards-compatible way
|
||||
color = ["dep:is-terminal", "dep:termcolor"]
|
||||
color = ["dep:anstyle", "dep:anstream"]
|
||||
help = []
|
||||
usage = []
|
||||
error-context = []
|
||||
|
@ -70,8 +63,8 @@ clap_lex = { path = "../clap_lex", version = "0.4.0" }
|
|||
bitflags = "1.2.0"
|
||||
unicase = { version = "2.6.0", optional = true }
|
||||
strsim = { version = "0.10.0", optional = true }
|
||||
is-terminal = { version = "0.4.1", optional = true }
|
||||
termcolor = { version = "1.1.1", optional = true }
|
||||
anstream = { version = "0.2.5", optional = true }
|
||||
anstyle = { version = "0.3.5", features = ["std"], optional = true }
|
||||
terminal_size = { version = "0.2.1", optional = true }
|
||||
backtrace = { version = "0.3.67", optional = true }
|
||||
unicode-width = { version = "0.1.9", optional = true }
|
||||
|
@ -87,3 +80,4 @@ snapbox = "0.4.10"
|
|||
shlex = "1.1.0"
|
||||
static_assertions = "1.1.0"
|
||||
unic-emoji-char = "0.9.0"
|
||||
color-print = "0.3.4"
|
||||
|
|
|
@ -4280,7 +4280,7 @@ impl Arg {
|
|||
styled.literal("-");
|
||||
styled.literal(s);
|
||||
}
|
||||
styled.extend(self.stylize_arg_suffix(required).into_iter());
|
||||
styled.push_styled(&self.stylize_arg_suffix(required));
|
||||
styled
|
||||
}
|
||||
|
||||
|
|
|
@ -4673,9 +4673,7 @@ impl Command {
|
|||
|
||||
pub(crate) fn write_version_err(&self, use_long: bool) -> StyledStr {
|
||||
let msg = self._render_version(use_long);
|
||||
let mut styled = StyledStr::new();
|
||||
styled.none(msg);
|
||||
styled
|
||||
StyledStr::from(msg)
|
||||
}
|
||||
|
||||
pub(crate) fn long_help_exists(&self) -> bool {
|
||||
|
|
|
@ -1,108 +1,89 @@
|
|||
/// Terminal-styling container
|
||||
///
|
||||
/// For now, this is the same as a [`Str`][crate::builder::Str]. This exists to reserve space in
|
||||
/// the API for exposing terminal styling.
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub struct StyledStr {
|
||||
#[cfg(feature = "color")]
|
||||
pieces: Vec<(Option<Style>, String)>,
|
||||
#[cfg(not(feature = "color"))]
|
||||
pieces: String,
|
||||
}
|
||||
/// Styling may be encoded as [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap_builder as clap;
|
||||
/// // `cstr!` converts tags to ANSI codes
|
||||
/// let after_help: &'static str = color_print::cstr!(
|
||||
/// r#"<bold><underline>Examples</underline></bold>
|
||||
///
|
||||
/// <dim>$</dim> <bold>mybin --input file.toml</bold>
|
||||
/// "#);
|
||||
///
|
||||
/// let cmd = clap::Command::new("mybin")
|
||||
/// .after_help(after_help) // The `&str` gets converted into a `StyledStr`
|
||||
/// // ...
|
||||
/// # ;
|
||||
/// ```
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct StyledStr(String);
|
||||
|
||||
impl StyledStr {
|
||||
/// Create an empty buffer
|
||||
#[cfg(feature = "color")]
|
||||
pub const fn new() -> Self {
|
||||
Self { pieces: Vec::new() }
|
||||
}
|
||||
|
||||
/// Create an empty buffer
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
pieces: String::new(),
|
||||
}
|
||||
Self(String::new())
|
||||
}
|
||||
|
||||
/// Display using [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code) styling
|
||||
#[cfg(feature = "color")]
|
||||
pub fn ansi(&self) -> impl std::fmt::Display + '_ {
|
||||
AnsiDisplay { styled: self }
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
pub(crate) fn header(&mut self, msg: impl Into<String>) {
|
||||
self.stylize_(Some(Style::Header), msg.into());
|
||||
self.stylize(Style::Header, msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn literal(&mut self, msg: impl Into<String>) {
|
||||
self.stylize_(Some(Style::Literal), msg.into());
|
||||
self.stylize(Style::Literal, msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn placeholder(&mut self, msg: impl Into<String>) {
|
||||
self.stylize_(Some(Style::Placeholder), msg.into());
|
||||
self.stylize(Style::Placeholder, msg.into());
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "error-context"), allow(dead_code))]
|
||||
pub(crate) fn good(&mut self, msg: impl Into<String>) {
|
||||
self.stylize_(Some(Style::Good), msg.into());
|
||||
self.stylize(Style::Good, msg.into());
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "error-context"), allow(dead_code))]
|
||||
pub(crate) fn warning(&mut self, msg: impl Into<String>) {
|
||||
self.stylize_(Some(Style::Warning), msg.into());
|
||||
self.stylize(Style::Warning, msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn error(&mut self, msg: impl Into<String>) {
|
||||
self.stylize_(Some(Style::Error), msg.into());
|
||||
self.stylize(Style::Error, msg.into());
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn hint(&mut self, msg: impl Into<String>) {
|
||||
self.stylize_(Some(Style::Hint), msg.into());
|
||||
self.stylize(Style::Hint, msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn none(&mut self, msg: impl Into<String>) {
|
||||
self.stylize_(None, msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn stylize(&mut self, style: impl Into<Option<Style>>, msg: impl Into<String>) {
|
||||
self.stylize_(style.into(), msg.into());
|
||||
self.0.push_str(&msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn trim(&mut self) {
|
||||
self.trim_start();
|
||||
self.trim_end();
|
||||
self.0 = self.0.trim().to_owned()
|
||||
}
|
||||
|
||||
pub(crate) fn trim_start(&mut self) {
|
||||
if let Some((_, item)) = self.iter_mut().next() {
|
||||
*item = item.trim_start().to_owned();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn trim_end(&mut self) {
|
||||
if let Some((_, item)) = self.pieces.last_mut() {
|
||||
*item = item.trim_end().to_owned();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn trim_end(&mut self) {
|
||||
self.pieces = self.pieces.trim_end().to_owned();
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) fn replace_newline_var(&mut self) {
|
||||
self.0 = self.0.replace("{n}", "\n");
|
||||
}
|
||||
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
|
||||
if let Some((_, first)) = self.iter_mut().next() {
|
||||
first.insert_str(0, initial);
|
||||
}
|
||||
self.0.insert_str(0, initial);
|
||||
|
||||
let mut line_sep = "\n".to_owned();
|
||||
line_sep.push_str(trailing);
|
||||
for (_, content) in self.iter_mut() {
|
||||
*content = content.replace('\n', &line_sep);
|
||||
}
|
||||
self.0 = self.0.replace('\n', &line_sep);
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "wrap_help"), feature = "help"))]
|
||||
|
@ -110,42 +91,57 @@ impl StyledStr {
|
|||
|
||||
#[cfg(feature = "wrap_help")]
|
||||
pub(crate) fn wrap(&mut self, hard_width: usize) {
|
||||
let mut new = String::with_capacity(self.0.len());
|
||||
|
||||
let mut last = 0;
|
||||
let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
|
||||
for (_, content) in self.iter_mut() {
|
||||
let mut total = Vec::new();
|
||||
for content in self.iter_text() {
|
||||
// Preserve styling
|
||||
let current = content.as_ptr() as usize - self.0.as_str().as_ptr() as usize;
|
||||
if last != current {
|
||||
new.push_str(&self.0.as_str()[last..current]);
|
||||
}
|
||||
last = current + content.len();
|
||||
|
||||
for (i, line) in content.split_inclusive('\n').enumerate() {
|
||||
if 0 < i {
|
||||
// start of a section does not imply newline
|
||||
// reset char count on newline, skipping the start as we might have carried
|
||||
// over from a prior block of styled text
|
||||
wrapper.reset();
|
||||
}
|
||||
let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
|
||||
.collect::<Vec<_>>();
|
||||
total.extend(wrapper.wrap(line));
|
||||
new.extend(wrapper.wrap(line));
|
||||
}
|
||||
let total = total.join("");
|
||||
*content = total;
|
||||
}
|
||||
if last != self.0.len() {
|
||||
new.push_str(&self.0.as_str()[last..]);
|
||||
}
|
||||
new = new.trim_end().to_owned();
|
||||
|
||||
self.trim_end();
|
||||
self.0 = new;
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
fn stylize_(&mut self, style: Option<Style>, msg: String) {
|
||||
fn stylize(&mut self, style: Style, msg: String) {
|
||||
if !msg.is_empty() {
|
||||
self.pieces.push((style, msg));
|
||||
use std::fmt::Write as _;
|
||||
|
||||
let style = style.as_style();
|
||||
let _ = write!(self.0, "{}{}{}", style.render(), msg, style.render_reset());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
fn stylize_(&mut self, _style: Option<Style>, msg: String) {
|
||||
self.pieces.push_str(&msg);
|
||||
fn stylize(&mut self, _style: Style, msg: String) {
|
||||
self.0.push_str(&msg);
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) fn display_width(&self) -> usize {
|
||||
let mut width = 0;
|
||||
for (_, c) in self.iter() {
|
||||
for c in self.iter_text() {
|
||||
width += crate::output::display_width(c);
|
||||
}
|
||||
width
|
||||
|
@ -153,84 +149,30 @@ impl StyledStr {
|
|||
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.pieces.is_empty()
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(feature = "help")]
|
||||
pub(crate) fn as_styled_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
|
||||
self.pieces.iter().map(|(s, c)| (*s, c.as_str()))
|
||||
pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
|
||||
anstream::adapter::strip_str(&self.0)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
|
||||
[(None, self.pieces.as_str())].into_iter()
|
||||
pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
|
||||
[self.0.as_str()].into_iter()
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
|
||||
self.pieces.iter_mut().map(|(s, c)| (*s, c))
|
||||
pub(crate) fn push_styled(&mut self, other: &Self) {
|
||||
self.0.push_str(&other.0);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
|
||||
[(None, &mut self.pieces)].into_iter()
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
|
||||
self.pieces.into_iter()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
|
||||
[(None, self.pieces)].into_iter()
|
||||
}
|
||||
|
||||
pub(crate) fn extend(
|
||||
&mut self,
|
||||
other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>,
|
||||
) {
|
||||
for (style, content) in other {
|
||||
self.stylize(style.into(), content.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn write_colored(&self, buffer: &mut termcolor::Buffer) -> std::io::Result<()> {
|
||||
use std::io::Write;
|
||||
use termcolor::WriteColor;
|
||||
|
||||
for (style, content) in &self.pieces {
|
||||
let mut color = termcolor::ColorSpec::new();
|
||||
match style {
|
||||
Some(Style::Header) => {
|
||||
color.set_bold(true);
|
||||
color.set_underline(true);
|
||||
}
|
||||
Some(Style::Literal) => {
|
||||
color.set_bold(true);
|
||||
}
|
||||
Some(Style::Placeholder) => {}
|
||||
Some(Style::Good) => {
|
||||
color.set_fg(Some(termcolor::Color::Green));
|
||||
}
|
||||
Some(Style::Warning) => {
|
||||
color.set_fg(Some(termcolor::Color::Yellow));
|
||||
}
|
||||
Some(Style::Error) => {
|
||||
color.set_fg(Some(termcolor::Color::Red));
|
||||
color.set_bold(true);
|
||||
}
|
||||
Some(Style::Hint) => {
|
||||
color.set_dimmed(true);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
ok!(buffer.set_color(&color));
|
||||
ok!(buffer.write_all(content.as_bytes()));
|
||||
ok!(buffer.reset());
|
||||
}
|
||||
pub(crate) fn write_to(&self, buffer: &mut dyn std::io::Write) -> std::io::Result<()> {
|
||||
ok!(buffer.write_all(self.0.as_bytes()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -245,9 +187,7 @@ impl Default for &'_ StyledStr {
|
|||
|
||||
impl From<std::string::String> for StyledStr {
|
||||
fn from(name: std::string::String) -> Self {
|
||||
let mut styled = StyledStr::new();
|
||||
styled.none(name);
|
||||
styled
|
||||
StyledStr(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,56 +213,31 @@ impl From<&'_ &'static str> for StyledStr {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for StyledStr {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
impl std::fmt::Write for StyledStr {
|
||||
#[inline]
|
||||
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
|
||||
self.0.push_str(s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for StyledStr {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.iter().map(cmp_key).cmp(other.iter().map(cmp_key))
|
||||
#[inline]
|
||||
fn write_char(&mut self, c: char) -> Result<(), std::fmt::Error> {
|
||||
self.0.push(c);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str) {
|
||||
let style = c.0.map(|s| s.as_usize());
|
||||
let content = c.1;
|
||||
(style, content)
|
||||
}
|
||||
|
||||
/// Color-unaware printing. Never uses coloring.
|
||||
impl std::fmt::Display for StyledStr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
for (_, content) in self.iter() {
|
||||
ok!(std::fmt::Display::fmt(content, f));
|
||||
for part in self.iter_text() {
|
||||
part.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
struct AnsiDisplay<'s> {
|
||||
styled: &'s StyledStr,
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
impl std::fmt::Display for AnsiDisplay<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let mut buffer = termcolor::Buffer::ansi();
|
||||
ok!(self
|
||||
.styled
|
||||
.write_colored(&mut buffer)
|
||||
.map_err(|_| std::fmt::Error));
|
||||
let buffer = buffer.into_inner();
|
||||
let buffer = ok!(String::from_utf8(buffer).map_err(|_| std::fmt::Error));
|
||||
ok!(std::fmt::Display::fmt(&buffer, f));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Style {
|
||||
Header,
|
||||
|
@ -335,15 +250,16 @@ pub(crate) enum Style {
|
|||
}
|
||||
|
||||
impl Style {
|
||||
fn as_usize(&self) -> usize {
|
||||
#[cfg(feature = "color")]
|
||||
fn as_style(&self) -> anstyle::Style {
|
||||
match self {
|
||||
Self::Header => 0,
|
||||
Self::Literal => 1,
|
||||
Self::Placeholder => 2,
|
||||
Self::Good => 3,
|
||||
Self::Warning => 4,
|
||||
Self::Error => 5,
|
||||
Self::Hint => 6,
|
||||
Style::Header => (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).into(),
|
||||
Style::Literal => anstyle::Effects::BOLD.into(),
|
||||
Style::Placeholder => anstyle::Style::default(),
|
||||
Style::Good => anstyle::AnsiColor::Green.on_default(),
|
||||
Style::Warning => anstyle::AnsiColor::Yellow.on_default(),
|
||||
Style::Error => anstyle::AnsiColor::Red.on_default() | anstyle::Effects::BOLD,
|
||||
Style::Hint => anstyle::Effects::DIMMED.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,13 +100,13 @@ impl ErrorFormatter for RichFormatter {
|
|||
styled.none("\n");
|
||||
styled.none(TAB);
|
||||
styled.good("note: ");
|
||||
styled.extend(suggestion.iter());
|
||||
styled.push_styled(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
let usage = error.get(ContextKind::Usage);
|
||||
if let Some(ContextValue::StyledStr(usage)) = usage {
|
||||
put_usage(&mut styled, usage.clone());
|
||||
put_usage(&mut styled, usage);
|
||||
}
|
||||
|
||||
try_help(&mut styled, error.inner.help_flag);
|
||||
|
@ -377,7 +377,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) ->
|
|||
pub(crate) fn format_error_message(
|
||||
message: &str,
|
||||
cmd: Option<&Command>,
|
||||
usage: Option<StyledStr>,
|
||||
usage: Option<&StyledStr>,
|
||||
) -> StyledStr {
|
||||
let mut styled = StyledStr::new();
|
||||
start_error(&mut styled);
|
||||
|
@ -400,9 +400,9 @@ fn singular_or_plural(n: usize) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
fn put_usage(styled: &mut StyledStr, usage: StyledStr) {
|
||||
fn put_usage(styled: &mut StyledStr, usage: &StyledStr) {
|
||||
styled.none("\n\n");
|
||||
styled.extend(usage.into_iter());
|
||||
styled.push_styled(usage);
|
||||
}
|
||||
|
||||
pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> {
|
||||
|
|
|
@ -806,7 +806,7 @@ impl Message {
|
|||
let mut message = String::new();
|
||||
std::mem::swap(s, &mut message);
|
||||
|
||||
let styled = format::format_error_message(&message, Some(cmd), usage);
|
||||
let styled = format::format_error_message(&message, Some(cmd), usage.as_ref());
|
||||
|
||||
*self = Self::Formatted(styled);
|
||||
}
|
||||
|
|
|
@ -34,40 +34,42 @@ impl Colorizer {
|
|||
impl Colorizer {
|
||||
#[cfg(feature = "color")]
|
||||
pub(crate) fn print(&self) -> std::io::Result<()> {
|
||||
use termcolor::{BufferWriter, ColorChoice as DepColorChoice};
|
||||
|
||||
let color_when = match self.color_when {
|
||||
ColorChoice::Always => DepColorChoice::Always,
|
||||
ColorChoice::Auto if is_a_tty(self.stream) => DepColorChoice::Auto,
|
||||
_ => DepColorChoice::Never,
|
||||
ColorChoice::Always => anstream::ColorChoice::Always,
|
||||
ColorChoice::Auto => anstream::ColorChoice::Auto,
|
||||
ColorChoice::Never => anstream::ColorChoice::Never,
|
||||
};
|
||||
|
||||
let writer = match self.stream {
|
||||
Stream::Stderr => BufferWriter::stderr(color_when),
|
||||
Stream::Stdout => BufferWriter::stdout(color_when),
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
let mut buffer = writer.buffer();
|
||||
ok!(self.content.write_colored(&mut buffer));
|
||||
writer.print(&buffer)
|
||||
self.content.write_to(writer)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "color"))]
|
||||
pub(crate) fn print(&self) -> std::io::Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
// [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();
|
||||
write!(stdout, "{}", self)
|
||||
self.content.write_to(&mut stdout)
|
||||
}
|
||||
Stream::Stderr => {
|
||||
let stderr = std::io::stderr();
|
||||
let mut stderr = stderr.lock();
|
||||
write!(stderr, "{}", self)
|
||||
self.content.write_to(&mut stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,12 +81,3 @@ impl std::fmt::Display for Colorizer {
|
|||
self.content.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
fn is_a_tty(stream: Stream) -> bool {
|
||||
use is_terminal::IsTerminal;
|
||||
match stream {
|
||||
Stream::Stdout => std::io::stdout().is_terminal(),
|
||||
Stream::Stderr => std::io::stderr().is_terminal(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,21 +10,15 @@ pub(crate) fn write_help(writer: &mut StyledStr, cmd: &Command, usage: &Usage<'_
|
|||
debug!("write_help");
|
||||
|
||||
if let Some(h) = cmd.get_override_help() {
|
||||
writer.extend(h.iter());
|
||||
writer.push_styled(h);
|
||||
} else {
|
||||
#[cfg(feature = "help")]
|
||||
{
|
||||
use super::AutoHelp;
|
||||
use super::HelpTemplate;
|
||||
if let Some(tmpl) = cmd.get_help_template() {
|
||||
for (style, content) in tmpl.iter() {
|
||||
if style.is_none() {
|
||||
HelpTemplate::new(writer, cmd, usage, use_long)
|
||||
.write_templated_help(content);
|
||||
} else {
|
||||
writer.stylize(style, content);
|
||||
}
|
||||
}
|
||||
HelpTemplate::new(writer, cmd, usage, use_long)
|
||||
.write_templated_help(tmpl.as_styled_str());
|
||||
} else {
|
||||
AutoHelp::new(writer, cmd, usage, use_long).write_help();
|
||||
}
|
||||
|
|
|
@ -166,11 +166,8 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
self.header("Usage:");
|
||||
}
|
||||
"usage" => {
|
||||
self.writer.extend(
|
||||
self.usage
|
||||
.create_usage_no_title(&[])
|
||||
.unwrap_or_default()
|
||||
.into_iter(),
|
||||
self.writer.push_styled(
|
||||
&self.usage.create_usage_no_title(&[]).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
"all-args" => {
|
||||
|
@ -284,9 +281,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
self.none("\n");
|
||||
}
|
||||
let mut output = output.clone();
|
||||
replace_newline_var(&mut output);
|
||||
output.replace_newline_var();
|
||||
output.wrap(self.term_w);
|
||||
self.writer.extend(output.into_iter());
|
||||
self.writer.push_styled(&output);
|
||||
if after_new_line {
|
||||
self.none("\n");
|
||||
}
|
||||
|
@ -304,9 +301,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
};
|
||||
if let Some(output) = before_help {
|
||||
let mut output = output.clone();
|
||||
replace_newline_var(&mut output);
|
||||
output.replace_newline_var();
|
||||
output.wrap(self.term_w);
|
||||
self.writer.extend(output.into_iter());
|
||||
self.writer.push_styled(&output);
|
||||
self.none("\n\n");
|
||||
}
|
||||
}
|
||||
|
@ -323,9 +320,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
if let Some(output) = after_help {
|
||||
self.none("\n\n");
|
||||
let mut output = output.clone();
|
||||
replace_newline_var(&mut output);
|
||||
output.replace_newline_var();
|
||||
output.wrap(self.term_w);
|
||||
self.writer.extend(output.into_iter());
|
||||
self.writer.push_styled(&output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -470,7 +467,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
self.none(TAB);
|
||||
self.short(arg);
|
||||
self.long(arg);
|
||||
self.writer.extend(arg.stylize_arg_suffix(None).into_iter());
|
||||
self.writer.push_styled(&arg.stylize_arg_suffix(None));
|
||||
self.align_to_about(arg, next_line_help, longest);
|
||||
|
||||
let about = if self.use_long {
|
||||
|
@ -580,7 +577,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
let trailing_indent = self.get_spaces(trailing_indent);
|
||||
|
||||
let mut help = about.clone();
|
||||
replace_newline_var(&mut help);
|
||||
help.replace_newline_var();
|
||||
if !spec_vals.is_empty() {
|
||||
if !help.is_empty() {
|
||||
let sep = if self.use_long && arg.is_some() {
|
||||
|
@ -602,7 +599,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
help.wrap(avail_chars);
|
||||
help.indent("", &trailing_indent);
|
||||
let help_is_empty = help.is_empty();
|
||||
self.writer.extend(help.into_iter());
|
||||
self.writer.push_styled(&help);
|
||||
if let Some(arg) = arg {
|
||||
const DASH_SPACE: usize = "- ".len();
|
||||
const COLON_SPACE: usize = ": ".len();
|
||||
|
@ -668,10 +665,10 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
};
|
||||
|
||||
let mut help = help.clone();
|
||||
replace_newline_var(&mut help);
|
||||
help.replace_newline_var();
|
||||
help.wrap(avail_chars);
|
||||
help.indent("", &trailing_indent);
|
||||
self.writer.extend(help.into_iter());
|
||||
self.writer.push_styled(&help);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -948,7 +945,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
|
|||
let width = sc_str.display_width();
|
||||
|
||||
self.none(TAB);
|
||||
self.writer.extend(sc_str.into_iter());
|
||||
self.writer.push_styled(&sc_str);
|
||||
if !next_line_help {
|
||||
self.spaces(longest + TAB_WIDTH - width);
|
||||
}
|
||||
|
@ -1021,12 +1018,6 @@ fn should_show_subcommand(subcommand: &Command) -> bool {
|
|||
!subcommand.is_hide_set()
|
||||
}
|
||||
|
||||
fn replace_newline_var(styled: &mut StyledStr) {
|
||||
for (_, content) in styled.iter_mut() {
|
||||
*content = content.replace("{n}", "\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn longest_filter(arg: &Arg) -> bool {
|
||||
arg.is_takes_value_set() || arg.get_long().is_some() || arg.get_short().is_none()
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ impl<'cmd> Usage<'cmd> {
|
|||
let mut styled = StyledStr::new();
|
||||
styled.header("Usage:");
|
||||
styled.none(" ");
|
||||
styled.extend(usage.into_iter());
|
||||
styled.push_styled(&usage);
|
||||
Some(styled)
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ impl<'cmd> Usage<'cmd> {
|
|||
// Short-circuit full usage creation since no args will be relevant
|
||||
styled.literal(name);
|
||||
} else {
|
||||
styled.extend(self.create_help_usage(false).into_iter());
|
||||
styled.push_styled(&self.create_help_usage(false));
|
||||
}
|
||||
styled.placeholder(" <");
|
||||
styled.placeholder(placeholder);
|
||||
|
@ -190,7 +190,7 @@ impl<'cmd> Usage<'cmd> {
|
|||
pub(crate) fn write_args(&self, incls: &[Id], force_optional: bool, styled: &mut StyledStr) {
|
||||
for required in self.get_args(incls, force_optional) {
|
||||
styled.none(" ");
|
||||
styled.extend(required.into_iter());
|
||||
styled.push_styled(&required);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,7 +280,7 @@ impl<'cmd> Usage<'cmd> {
|
|||
let styled = required_positionals[index].take().unwrap();
|
||||
let mut new = StyledStr::new();
|
||||
new.literal("-- ");
|
||||
new.extend(styled.into_iter());
|
||||
new.push_styled(&styled);
|
||||
required_positionals[index] = Some(new);
|
||||
}
|
||||
} else {
|
||||
|
@ -288,7 +288,7 @@ impl<'cmd> Usage<'cmd> {
|
|||
if pos.is_last_set() {
|
||||
styled = StyledStr::new();
|
||||
styled.literal("[-- ");
|
||||
styled.extend(pos.stylized(Some(true)).into_iter());
|
||||
styled.push_styled(&pos.stylized(Some(true)));
|
||||
styled.literal("]");
|
||||
} else {
|
||||
styled = pos.stylized(Some(false));
|
||||
|
|
Loading…
Reference in a new issue