Merge pull request #4765 from epage/anstream

feat(help): Allow user-provided styled text in `StyledStr`
This commit is contained in:
Ed Page 2023-03-27 21:45:21 -05:00 committed by GitHub
commit 52eab28f1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 195 additions and 271 deletions

50
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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
}

View file

@ -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 {

View file

@ -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(),
}
}
}

View file

@ -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> {

View file

@ -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);
}

View file

@ -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(),
}
}

View file

@ -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();
}

View file

@ -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()
}

View file

@ -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));