1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
use {
clap::{ArgAction, Command},
std::collections::HashMap,
termimad::{
minimad::{OwningTemplateExpander, TextTemplate},
FmtText, MadSkin,
},
};
/// Default template for the "title" section
pub static TEMPLATE_TITLE: &str = "# **${name}** ${version}";
/// Default template for the "author" section
pub static TEMPLATE_AUTHOR: &str = "
*by* ${author}
";
/// Default template for the "usage" section
pub static TEMPLATE_USAGE: &str = "
**Usage: ** `${name} [options]${positional-args}`
";
/// Default template for the "positionals" section
pub static TEMPLATE_POSITIONALS: &str = "
${positional-lines
* `${key}` : ${help}
}
";
/// Default template for the "options" section
pub static TEMPLATE_OPTIONS: &str = "
**Options:**
|:-:|:-:|:-:|:-|
|short|long|value|description|
|:-:|:-|:-:|:-|
${option-lines
|${short}|${long}|${value}|${help}${possible_values}${default}|
}
|-
";
/// a template for the "options" section with the value merged to short and long
pub static TEMPLATE_OPTIONS_MERGED_VALUE: &str = "
**Options:**
|:-:|:-:|:-|
|short|long|description|
|:-:|:-|:-|
${option-lines
|${short} *${value-short-braced}*|${long} *${value-long-braced}*|${help}${possible_values}${default}|
}
|-
";
/// Keys used to enable/disable/change templates
pub static TEMPLATES: &[&str] = &[
"title",
"author",
"introduction",
"usage",
"positionals",
"options",
"bugs",
];
/// An object which you can configure to print the help of a command
///
/// For example, changing the color of bold text and using an alternate
/// template for the options section:
///
/// ```rust
/// use clap::{CommandFactory, Parser, ValueEnum};
/// use clap_help::Printer;
///
/// #[derive(Parser, Debug)]
/// #[command(author, version, about, disable_help_flag = true)]
/// struct Args {
///
/// /// Print help
/// #[arg(long)]
/// help: bool,
///
/// /// Comma separated list of features
/// #[clap(long, value_name = "features")]
/// pub features: Option<String>,
/// }
///
/// fn main() {
/// let args = Args::parse();
/// if args.help {
/// let mut printer = clap_help::Printer::new(Args::command())
/// .with("options", clap_help::TEMPLATE_OPTIONS_MERGED_VALUE);
/// printer.skin_mut().bold.set_fg(ansi(204));
/// printer.print_help();
/// return;
/// }
/// // rest of the program
/// }
///
/// ```
pub struct Printer<'t> {
skin: MadSkin,
expander: OwningTemplateExpander<'static>,
template_keys: Vec<&'static str>,
templates: HashMap<&'static str, &'t str>,
pub full_width: bool,
pub max_width: Option<usize>,
}
impl<'t> Printer<'t> {
pub fn new(mut cmd: Command) -> Self {
cmd.build();
let expander = Self::make_expander(&cmd);
let mut templates = HashMap::new();
templates.insert("title", TEMPLATE_TITLE);
templates.insert("author", TEMPLATE_AUTHOR);
templates.insert("usage", TEMPLATE_USAGE);
templates.insert("positionals", TEMPLATE_POSITIONALS);
templates.insert("options", TEMPLATE_OPTIONS);
Self {
skin: Self::make_skin(),
expander,
templates,
template_keys: TEMPLATES.to_vec(),
full_width: false,
max_width: None,
}
}
/// Build a skin for the detected theme of the terminal
/// (i.e. dark, light, or other)
pub fn make_skin() -> MadSkin {
match terminal_light::luma() {
Ok(luma) if luma > 0.85 => MadSkin::default_light(),
Ok(luma) if luma < 0.2 => MadSkin::default_dark(),
_ => MadSkin::default(),
}
}
/// Use the provided skin
pub fn with_skin(mut self, skin: MadSkin) -> Self {
self.skin = skin;
self
}
/// Set a maximal width, so that the whole terminal width isn't used.
///
/// This may make some long sentences easier to read on super wide
/// terminals, especially when the whole text is short.
/// Depending on your texts and parameters, you may set up a width
/// of 100 or 150.
pub fn with_max_width(mut self, w: usize) -> Self {
self.max_width = Some(w);
self
}
/// Give a mutable reference to the current skin
/// (by default the automatically selected one)
/// so that it can be modified
pub fn skin_mut(&mut self) -> &mut MadSkin {
&mut self.skin
}
/// Change a template
pub fn set_template(&mut self, key: &'static str, template: &'t str) {
self.templates.insert(key, template);
}
/// Change or add a template
pub fn with(mut self, key: &'static str, template: &'t str) -> Self {
self.set_template(key, template);
self
}
/// Unset a template
pub fn without(mut self, key: &'static str) -> Self {
self.templates.remove(key);
self
}
/// A mutable reference to the list of template keys, so that you can
/// insert new keys, or change their order.
/// Any key without matching template will just be ignored
pub fn template_keys_mut(&mut self) -> &mut Vec<&'static str> {
&mut self.template_keys
}
/// A mutable reference to the list of template keys, so that you can
/// insert new keys, or change their order.
/// Any key without matching template will just be ignored
#[deprecated(since = "0.6.2", note = "use template_keys_mut instead")]
pub fn template_order_mut(&mut self) -> &mut Vec<&'static str> {
&mut self.template_keys
}
fn make_expander(cmd: &Command) -> OwningTemplateExpander<'static> {
let mut expander = OwningTemplateExpander::new();
expander.set_default("");
let name = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name());
expander.set("name", name);
if let Some(author) = cmd.get_author() {
expander.set("author", author);
}
if let Some(version) = cmd.get_version() {
expander.set("version", version);
}
let options = cmd
.get_arguments()
.filter(|a| a.get_short().is_some() || a.get_long().is_some());
for arg in options {
let sub = expander.sub("option-lines");
if let Some(short) = arg.get_short() {
sub.set("short", format!("-{short}"));
}
if let Some(long) = arg.get_long() {
sub.set("long", format!("--{long}"));
}
if let Some(help) = arg.get_help() {
sub.set_md("help", help.to_string());
}
if arg.get_action().takes_values() {
if let Some(name) = arg.get_value_names().and_then(|arr| arr.first()) {
sub.set("value", name);
let braced = format!("<{}>", name);
sub.set("value-braced", &braced);
if arg.get_short().is_some() {
sub.set("value-short-braced", &braced);
sub.set("value-short", name);
}
if arg.get_long().is_some() {
sub.set("value-long-braced", &braced);
sub.set("value-long", name);
}
};
}
let mut possible_values = arg.get_possible_values();
if !possible_values.is_empty() {
let possible_values: Vec<String> = possible_values
.drain(..)
.map(|v| format!("`{}`", v.get_name()))
.collect();
expander.sub("option-lines").set_md(
"possible_values",
format!(" Possible values: [{}]", possible_values.join(", ")),
);
}
if let Some(default) = arg.get_default_values().first() {
match arg.get_action() {
ArgAction::Set | ArgAction::Append => {
expander.sub("option-lines").set_md(
"default",
format!(" Default: `{}`", default.to_string_lossy()),
);
}
_ => {}
}
}
}
let mut args = String::new();
for arg in cmd.get_positionals() {
let Some(key) = arg.get_value_names().and_then(|arr| arr.first()) else {
continue;
};
args.push(' ');
if !arg.is_required_set() {
args.push('[');
}
if arg.is_last_set() {
args.push_str("-- ");
}
args.push_str(key);
if !arg.is_required_set() {
args.push(']');
}
let sub = expander.sub("positional-lines");
sub.set("key", key);
if let Some(help) = arg.get_help() {
sub.set("help", help);
}
}
expander.set("positional-args", args);
expander
}
/// Give you a mut reference to the expander, so that you can overload
/// the variable of the expander used to fill the templates of the help,
/// or add new variables for your own templates
pub fn expander_mut(&mut self) -> &mut OwningTemplateExpander<'static> {
&mut self.expander
}
/// Print the provided template with the printer's expander
///
/// It's normally more convenient to change template_keys or some
/// templates, unless you want none of the standard templates
pub fn print_template(&self, template: &str) {
self.skin.print_owning_expander_md(&self.expander, template);
}
/// Print all the templates, in order
pub fn print_help(&self) {
if self.full_width {
self.print_help_full_width()
} else {
self.print_help_content_width()
}
}
fn print_help_full_width(&self) {
for key in &self.template_keys {
if let Some(template) = self.templates.get(key) {
self.print_template(template);
}
}
}
fn print_help_content_width(&self) {
let (width, _) = termimad::terminal_size();
let mut width = width as usize;
if let Some(max_width) = self.max_width {
width = width.min(max_width);
}
let mut texts: Vec<FmtText> = self
.template_keys
.iter()
.filter_map(|key| self.templates.get(key))
.map(|&template| {
let template = TextTemplate::from(template);
let text = self.expander.expand(&template);
FmtText::from_text(&self.skin, text, Some(width))
})
.collect();
let content_width = texts
.iter()
.fold(0, |cw, text| cw.max(text.content_width()));
for text in &mut texts {
text.set_rendering_width(content_width);
println!("{}", text);
}
}
}