Auto merge of #12578 - epage:help, r=weihanglo

feat(help): Add styling to help output

### What does this PR try to resolve?

Try to make `--help` output easier to parse by using terminal styling

Screenshots:

![Screenshot from 2023-09-06 09-57-11](https://github.com/rust-lang/cargo/assets/60961/61069af4-ef05-40ad-9240-fedea44d4c71)

![Screenshot from 2023-09-06 09-57-21](https://github.com/rust-lang/cargo/assets/60961/d2e69024-42aa-47c0-ad0f-24e43551b8db)

![Screenshot from 2023-09-06 09-57-36](https://github.com/rust-lang/cargo/assets/60961/e3d895e2-745f-48c6-9e84-d6fb67198d6d)

*(`nargo` is my shell script wrapping `cargo run --manifest-path cargo/Cargo.toml`)*

### How should we test and review this PR?

At this time, the only styling snapshotting library I know of is a pain to use, so testing this requires manually running the commands which I did.  Screenshots are included for easier evaluation of the general idea.

Snapshotting of the plain text output ensures we don't have accidental formatting regressions from this change since the formatting isn't as obvious from looking at the code.

### Additional information

Traditionally, cargo has disabled clap's styled output.  My assumed
reason is that cargo mixes custom help output with auto-generated and
you couldn't previously make it all styled.
Clap 4.2 allowed users to pass in strings styled using ANSI escape
codes, allowing us to pass in styled text that matches clap, unblocking this.  In clap
4.4.1, clap gained the ability for the user to override the style.

In this PR, I decided to use the new 4.4.1 feature to style clap's
output to match the rest of cargo's output.  Alternatively, we could use
a more subdue style that clap uses by default.

I used the `color-print` crate to allow something almost html-like for styling `&static str`.  Alternatively, we could directly embed the ANSI escape codes harder to get write, harder to inspect), or we could do the styling at runtime and enable the `string` feature in clap.

I decided to *not* style `Arg::help` messages because
- It might be distracting to have the descriptions lit up like a
  christmas tree
- It is a lot more work

The one exception I made was for `--list` since it is for a
psuedo-command (`...`) and I wanted to intentionally draw attention to
it.

#12593 made styling of `cargo -h` cleaner imo.
#12592 and #12594 were improvements I noticed while doing this.
This commit is contained in:
bors 2023-09-11 06:25:47 +00:00
commit c2d26f32d3
39 changed files with 175 additions and 73 deletions

22
Cargo.lock generated
View File

@ -270,6 +270,7 @@ dependencies = [
"cargo-test-support",
"cargo-util",
"clap",
"color-print",
"crates-io",
"curl",
"curl-sys",
@ -540,6 +541,27 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807"
[[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 = "colorchoice"
version = "1.0.0"

View File

@ -30,6 +30,7 @@ cargo-test-support = { path = "crates/cargo-test-support" }
cargo-util = { version = "0.2.6", path = "crates/cargo-util" }
cargo_metadata = "0.17.0"
clap = "4.4.2"
color-print = "0.3.4"
core-foundation = { version = "0.9.3", features = ["mac_os_10_7_support"] }
crates-io = { version = "0.39.0", path = "crates/crates-io" }
criterion = { version = "0.5.1", features = ["html_reports"] }
@ -129,6 +130,7 @@ cargo-credential-libsecret.workspace = true
cargo-credential-macos-keychain.workspace = true
cargo-credential-wincred.workspace = true
cargo-util.workspace = true
color-print.workspace = true
clap = { workspace = true, features = ["wrap_help"] }
crates-io.workspace = true
curl = { workspace = true, features = ["http2"] }

View File

@ -513,10 +513,23 @@ impl GlobalArgs {
pub fn cli() -> Command {
let usage = if is_rustup() {
"cargo [+toolchain] [OPTIONS] [COMMAND]\n cargo [+toolchain] [OPTIONS] -Zscript <MANIFEST_RS> [ARGS]..."
color_print::cstr!("<cyan,bold>cargo</> <cyan>[+toolchain] [OPTIONS] [COMMAND]</>\n <cyan,bold>cargo</> <cyan>[+toolchain] [OPTIONS]</> <cyan,bold>-Zscript</> <cyan><<MANIFEST_RS>> [ARGS]...</>")
} else {
"cargo [OPTIONS] [COMMAND]\n cargo [OPTIONS] -Zscript <MANIFEST_RS> [ARGS]..."
color_print::cstr!("<cyan,bold>cargo</> <cyan>[OPTIONS] [COMMAND]</>\n <cyan,bold>cargo</> <cyan>[OPTIONS]</> <cyan,bold>-Zscript</> <cyan><<MANIFEST_RS>> [ARGS]...</>")
};
let styles = {
use clap::builder::styling::*;
Styles::styled()
.header(AnsiColor::Green.on_default() | Effects::BOLD)
.usage(AnsiColor::Green.on_default() | Effects::BOLD)
.literal(AnsiColor::Cyan.on_default() | Effects::BOLD)
.placeholder(AnsiColor::Cyan.on_default())
.error(AnsiColor::Red.on_default() | Effects::BOLD)
.valid(AnsiColor::Cyan.on_default() | Effects::BOLD)
.invalid(AnsiColor::Yellow.on_default() | Effects::BOLD)
};
Command::new("cargo")
// Subcommands all count their args' display order independently (from 0),
// which makes their args interspersed with global args. This puts global args last.
@ -524,42 +537,40 @@ pub fn cli() -> Command {
// We also want these to come before auto-generated `--help`
.next_display_order(800)
.allow_external_subcommands(true)
// Doesn't mix well with our list of common cargo commands. See clap-rs/clap#3108 for
// opening clap up to allow us to style our help template
.disable_colored_help(true)
.styles(styles)
// Provide a custom help subcommand for calling into man pages
.disable_help_subcommand(true)
.override_usage(usage)
.help_template(
.help_template(color_print::cstr!(
"\
Rust's package manager
Usage: {usage}
<green,bold>Usage:</> {usage}
Options:
<green,bold>Options:</>
{options}
Commands:
build, b Compile the current package
check, c Analyze the current package and report errors, but don't build object files
clean Remove the target directory
doc, d Build this package's and its dependencies' documentation
new Create a new cargo package
init Create a new cargo package in an existing directory
add Add dependencies to a manifest file
remove Remove dependencies from a manifest file
run, r Run a binary or example of the local package
test, t Run the tests
bench Run the benchmarks
update Update dependencies listed in Cargo.lock
search Search registry for crates
publish Package and upload this package to the registry
install Install a Rust binary. Default location is $HOME/.cargo/bin
uninstall Uninstall a Rust binary
... See all commands with --list
<green,bold>Commands:</>
<cyan,bold>build</>, <cyan,bold>b</> Compile the current package
<cyan,bold>check</>, <cyan,bold>c</> Analyze the current package and report errors, but don't build object files
<cyan,bold>clean</> Remove the target directory
<cyan,bold>doc</>, <cyan,bold>d</> Build this package's and its dependencies' documentation
<cyan,bold>new</> Create a new cargo package
<cyan,bold>init</> Create a new cargo package in an existing directory
<cyan,bold>add</> Add dependencies to a manifest file
<cyan,bold>remove</> Remove dependencies from a manifest file
<cyan,bold>run</>, <cyan,bold>r</> Run a binary or example of the local package
<cyan,bold>test</>, <cyan,bold>t</> Run the tests
<cyan,bold>bench</> Run the benchmarks
<cyan,bold>update</> Update dependencies listed in Cargo.lock
<cyan,bold>search</> Search registry for crates
<cyan,bold>publish</> Package and upload this package to the registry
<cyan,bold>install</> Install a Rust binary. Default location is $HOME/.cargo/bin
<cyan,bold>uninstall</> Uninstall a Rust binary
<cyan>...</> See all commands with <cyan,bold>--list</>
See 'cargo help <command>' for more information on a specific command.\n",
)
See '<cyan,bold>cargo help</> <cyan><<command>></>' for more information on a specific command.\n",
))
.arg(flag("version", "Print version info and exit").short('V'))
.arg(flag("list", "List installed commands"))
.arg(

View File

@ -18,12 +18,12 @@ pub fn cli() -> Command {
clap::Command::new("add")
.about("Add dependencies to a Cargo.toml manifest file")
.override_usage(
"\
cargo add [OPTIONS] <DEP>[@<VERSION>] ...
cargo add [OPTIONS] --path <PATH> ...
cargo add [OPTIONS] --git <URL> ..."
)
.after_help("Run `cargo help add` for more detailed information.\n")
color_print::cstr!("\
<cyan,bold>cargo add</> <cyan>[OPTIONS] <<DEP>>[@<<VERSION>>] ...</>
<cyan,bold>cargo add</> <cyan>[OPTIONS]</> <cyan,bold>--path</> <cyan><<PATH>> ...</>
<cyan,bold>cargo add</> <cyan>[OPTIONS]</> <cyan,bold>--git</> <cyan><<URL>> ...</>"
))
.after_help(color_print::cstr!("Run `<cyan,bold>cargo help add</>` for more detailed information.\n"))
.group(clap::ArgGroup::new("selected").multiple(true).required(true))
.args([
clap::Arg::new("crates")

View File

@ -50,7 +50,9 @@ pub fn cli() -> Command {
.arg_unit_graph()
.arg_timings()
.arg_manifest_path()
.after_help("Run `cargo help bench` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help bench</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -46,7 +46,9 @@ pub fn cli() -> Command {
.arg_unit_graph()
.arg_timings()
.arg_manifest_path()
.after_help("Run `cargo help build` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help build</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -37,7 +37,9 @@ pub fn cli() -> Command {
.arg_unit_graph()
.arg_timings()
.arg_manifest_path()
.after_help("Run `cargo help check` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help check</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -14,7 +14,9 @@ pub fn cli() -> Command {
.arg_target_triple("Target triple to clean output for")
.arg_target_dir()
.arg_manifest_path()
.after_help("Run `cargo help clean` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help clean</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -40,7 +40,9 @@ pub fn cli() -> Command {
.arg_unit_graph()
.arg_timings()
.arg_manifest_path()
.after_help("Run `cargo help doc` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help doc</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -9,7 +9,9 @@ pub fn cli() -> Command {
.arg_quiet()
.arg_target_triple("Fetch dependencies for the target triple")
.arg_manifest_path()
.after_help("Run `cargo help fetch` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help fetch</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -54,7 +54,9 @@ pub fn cli() -> Command {
.arg_target_dir()
.arg_timings()
.arg_manifest_path()
.after_help("Run `cargo help fix` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help fix</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -7,7 +7,9 @@ pub fn cli() -> Command {
.about("Generate the lockfile for a package")
.arg_quiet()
.arg_manifest_path()
.after_help("Run `cargo help generate-lockfile` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help generate-lockfile</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -9,7 +9,9 @@ pub fn cli() -> Command {
.arg_new_opts()
.arg(opt("registry", "Registry to use").value_name("REGISTRY"))
.arg_quiet()
.after_help("Run `cargo help init` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help init</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -87,7 +87,9 @@ pub fn cli() -> Command {
.arg_target_triple("Build for the target triple")
.arg_target_dir()
.arg_timings()
.after_help("Run `cargo help install` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help install</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -16,7 +16,9 @@ pub fn cli() -> Command {
)
.arg_quiet()
.arg_manifest_path()
.after_help("Run `cargo help locate-project` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help locate-project</>` for more detailed information.\n"
))
}
#[derive(Serialize)]

View File

@ -14,7 +14,9 @@ pub fn cli() -> Command {
.last(true),
)
.arg_quiet()
.after_help("Run `cargo help login` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help login</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -6,7 +6,9 @@ pub fn cli() -> Command {
.about("Remove an API token from the registry locally")
.arg(opt("registry", "Registry to use").value_name("REGISTRY"))
.arg_quiet()
.after_help("Run `cargo help logout` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help logout</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -26,7 +26,9 @@ pub fn cli() -> Command {
.arg_quiet()
.arg_features()
.arg_manifest_path()
.after_help("Run `cargo help metadata` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help metadata</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -9,7 +9,9 @@ pub fn cli() -> Command {
.arg_new_opts()
.arg(opt("registry", "Registry to use").value_name("REGISTRY"))
.arg_quiet()
.after_help("Run `cargo help new` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help new</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -28,7 +28,9 @@ pub fn cli() -> Command {
.arg(opt("token", "API token to use when authenticating").value_name("TOKEN"))
.arg(opt("registry", "Registry to use").value_name("REGISTRY"))
.arg_quiet()
.after_help("Run `cargo help owner` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help owner</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -35,7 +35,9 @@ pub fn cli() -> Command {
.arg_target_dir()
.arg_parallel()
.arg_manifest_path()
.after_help("Run `cargo help package` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help package</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -10,7 +10,9 @@ pub fn cli() -> Command {
.arg_quiet()
.arg_package("Argument to get the package ID specifier for")
.arg_manifest_path()
.after_help("Run `cargo help pkgid` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help pkgid</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -24,7 +24,9 @@ pub fn cli() -> Command {
.arg_target_triple("Build for the target triple")
.arg_target_dir()
.arg_manifest_path()
.after_help("Run `cargo help publish` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help publish</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -2,13 +2,13 @@ use crate::command_prelude::*;
pub fn cli() -> Command {
subcommand("read-manifest")
.about(
.about(color_print::cstr!(
"\
Print a JSON representation of a Cargo.toml manifest.
Deprecated, use `cargo metadata --no-deps` instead.\
",
)
Deprecated, use `<cyan,bold>cargo metadata --no-deps</>` instead.\
"
))
.arg_quiet()
.arg_manifest_path()
}

View File

@ -50,6 +50,9 @@ pub fn cli() -> clap::Command {
])
.arg_package("Package to remove from")
.arg_manifest_path()
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help remove</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -5,7 +5,9 @@ use cargo::drop_println;
pub fn cli() -> Command {
subcommand("report")
.about("Generate and display various kinds of reports")
.after_help("Run `cargo help report` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help report</>` for more detailed information.\n"
))
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(

View File

@ -38,7 +38,9 @@ pub fn cli() -> Command {
.arg_manifest_path()
.arg_unit_graph()
.arg_timings()
.after_help("Run `cargo help run` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help run</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -52,7 +52,9 @@ pub fn cli() -> Command {
.arg_unit_graph()
.arg_timings()
.arg_manifest_path()
.after_help("Run `cargo help rustc` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help rustc</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -40,7 +40,9 @@ pub fn cli() -> Command {
.arg_unit_graph()
.arg_timings()
.arg_manifest_path()
.after_help("Run `cargo help rustdoc` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help rustdoc</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -18,7 +18,9 @@ pub fn cli() -> Command {
.arg_index()
.arg(opt("registry", "Registry to use").value_name("REGISTRY"))
.arg_quiet()
.after_help("Run `cargo help search` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help search</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -57,10 +57,10 @@ pub fn cli() -> Command {
.arg_unit_graph()
.arg_timings()
.arg_manifest_path()
.after_help(
"Run `cargo help test` for more detailed information.\n\
Run `cargo test -- --help` for test binary options.\n",
)
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help test</>` for more detailed information.\n\
Run `<cyan,bold>cargo test -- --help</>` for test binary options.\n",
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -96,7 +96,9 @@ pub fn cli() -> Command {
Pass `all` to include all targets.",
)
.arg_manifest_path()
.after_help("Run `cargo help tree` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help tree</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -13,7 +13,9 @@ pub fn cli() -> Command {
multi_opt("bin", "NAME", "Only uninstall the binary NAME")
.help_heading(heading::TARGET_SELECTION),
)
.after_help("Run `cargo help uninstall` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help uninstall</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -42,7 +42,9 @@ pub fn cli() -> Command {
.help_heading(heading::PACKAGE_SELECTION),
)
.arg_manifest_path()
.after_help("Run `cargo help update` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help update</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -38,7 +38,9 @@ pub fn cli() -> Command {
.arg(unsupported("disallow-duplicates"))
.arg_quiet()
.arg_manifest_path()
.after_help("Run `cargo help vendor` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help vendor</>` for more detailed information.\n"
))
}
fn unsupported(name: &'static str) -> Arg {

View File

@ -8,7 +8,9 @@ pub fn cli() -> Command {
.about("Check correctness of crate manifest")
.arg_quiet()
.arg_manifest_path()
.after_help("Run `cargo help verify-project` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help verify-project</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -5,7 +5,9 @@ pub fn cli() -> Command {
subcommand("version")
.about("Show version information")
.arg_quiet()
.after_help("Run `cargo help version` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help version</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -20,7 +20,9 @@ pub fn cli() -> Command {
.arg(opt("registry", "Registry to use").value_name("REGISTRY"))
.arg(opt("token", "API token to use when authenticating").value_name("TOKEN"))
.arg_quiet()
.after_help("Run `cargo help yank` for more detailed information.\n")
.after_help(color_print::cstr!(
"Run `<cyan,bold>cargo help yank</>` for more detailed information.\n"
))
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

View File

@ -27,3 +27,5 @@ Manifest Options:
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
Run `cargo help remove` for more detailed information.