Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ authors = ["Jeff Dickey @jdx"]
license = "MIT"

[workspace.dependencies]
clap_usage = { path = "./clap_usage", version = "2.0.3" }
clap_usage = { path = "./clap_usage", version = "4.0.0" }
usage-cli = { path = "./cli" }
usage-lib = { path = "./lib", version = "4.0.0", features = ["clap"] }

Expand Down
2 changes: 1 addition & 1 deletion clap_usage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "clap_usage"
edition = "2021"
version = "2.0.3"
version = "4.0.0"
include = [
"/Cargo.toml",
"/Cargo.lock",
Expand Down
26 changes: 25 additions & 1 deletion clap_usage/src/generate.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
use clap::Command;
use std::io::Write;

pub fn generate<S: Into<String>>(cmd: &mut Command, bin_name: S, buf: &mut dyn Write) {
/// The usage spec for a clap command, ready to inspect or annotate.
///
/// [`generate`] writes this straight out. Take it here instead when you need
/// to set something clap cannot express — most often `effect=`, which says
/// whether a command reads, writes or destroys:
///
/// ```no_run
/// # use clap::Command;
/// # use clap_usage::usage::SpecCommandEffect;
/// # let mut cmd = Command::new("mycli").subcommand(Command::new("rm"));
/// let mut spec = clap_usage::spec(&mut cmd, "mycli");
/// if let Some(rm) = spec.cmd.subcommands.get_mut("rm") {
/// rm.effect = Some(SpecCommandEffect::Destructive);
/// }
/// println!("{spec}");
/// ```
pub fn spec<S: Into<String>>(cmd: &mut Command, bin_name: S) -> usage::Spec {
let mut spec: usage::Spec = cmd.clone().into();
spec.bin = bin_name.into();
spec
}

/// Write the usage spec for a clap command, with the `@generated` header.
///
/// Use [`spec`] instead if you need to modify the spec before writing it.
pub fn generate<S: Into<String>>(cmd: &mut Command, bin_name: S, buf: &mut dyn Write) {
let spec = spec(cmd, bin_name);

writeln!(buf, "// @generated by usage-cli from clap metadata")
.expect("write @generated comment");
Expand Down
21 changes: 20 additions & 1 deletion clap_usage/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
//! Build [`usage`] specs from [`clap`] commands.
//!
//! [`generate`] writes a spec straight out; [`spec`] hands you the [`usage::Spec`]
//! first so you can set things clap cannot express.
//!
//! The `usage` crate is re-exported, so depending on `clap_usage` alone is
//! enough to name the spec types you get back:
//!
//! ```no_run
//! use clap_usage::usage::SpecCommandEffect;
//! ```

mod generate;

pub use crate::generate::generate;
/// The [`usage`] crate, re-exported.
///
/// [`spec`] returns `usage` types, so consumers need to name them. Re-exporting
/// the whole crate means `clap_usage` on its own is a sufficient dependency,
/// and nothing here goes stale as `usage` grows.
pub use usage;

pub use crate::generate::{generate, spec};
Comment thread
greptile-apps[bot] marked this conversation as resolved.
35 changes: 35 additions & 0 deletions clap_usage/tests/spec_accessor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use clap::Command;
// Deliberately reached through clap_usage, proving a consumer needs no
// direct usage-lib dependency to annotate the spec it gets back.
use clap_usage::usage::SpecCommandEffect;

/// The reason `spec` exists: `generate` writes straight to a writer, so there
/// is no way to set something clap cannot express, such as `effect=`.
#[test]
fn spec_can_be_annotated_before_rendering() {
let mut cmd = Command::new("mycli")
.subcommand(Command::new("ls").about("List things"))
.subcommand(Command::new("rm").about("Remove things"));

let mut spec = clap_usage::spec(&mut cmd, "mycli");
assert_eq!(spec.bin, "mycli");

spec.cmd.subcommands.get_mut("ls").unwrap().effect = Some(SpecCommandEffect::Read);
spec.cmd.subcommands.get_mut("rm").unwrap().effect = Some(SpecCommandEffect::Destructive);

let rendered = spec.to_string();
assert!(rendered.contains("cmd ls"), "{rendered}");
assert!(rendered.contains("effect=read"), "{rendered}");
assert!(rendered.contains("effect=destructive"), "{rendered}");
}

/// `generate` must keep producing exactly what it always did.
#[test]
fn generate_still_writes_the_header_and_spec() {
let mut cmd = Command::new("mycli").subcommand(Command::new("ls"));
let mut buf = vec![];
clap_usage::generate(&mut cmd, "mycli", &mut buf);
let out = String::from_utf8(buf).unwrap();
assert!(out.starts_with("// @generated by usage-cli from clap metadata\n"));
assert!(out.contains("bin mycli"));
}