1use {
2 clap::{ArgAction, Command},
3 std::collections::HashMap,
4 termimad::{
5 minimad::{OwningTemplateExpander, TextTemplate},
6 FmtText, MadSkin,
7 },
8};
9
10pub static TEMPLATE_TITLE: &str = "# **${name}** ${version}";
12
13pub static TEMPLATE_AUTHOR: &str = "
15*by* ${author}
16";
17
18pub static TEMPLATE_USAGE: &str = "
20**Usage: ** `${name} [options]${positional-args}`
21";
22
23pub static TEMPLATE_POSITIONALS: &str = "
25${positional-lines
26* `${key}` : ${help}
27}
28";
29
30pub static TEMPLATE_OPTIONS: &str = "
32**Options:**
33|:-:|:-:|:-:|:-|
34|short|long|value|description|
35|:-:|:-|:-:|:-|
36${option-lines
37|${short}|${long}|${value}|${help}${possible_values}${default}|
38}
39|-
40";
41
42pub static TEMPLATE_OPTIONS_MERGED_VALUE: &str = "
44**Options:**
45|:-:|:-:|:-|
46|short|long|description|
47|:-:|:-|:-|
48${option-lines
49|${short} *${value-short-braced}*|${long} *${value-long-braced}*|${help}${possible_values}${default}|
50}
51|-
52";
53
54pub static TEMPLATES: &[&str] = &[
56 "title",
57 "author",
58 "introduction",
59 "usage",
60 "positionals",
61 "options",
62 "bugs",
63];
64
65pub struct Printer<'t> {
101 skin: MadSkin,
102 expander: OwningTemplateExpander<'static>,
103 template_keys: Vec<&'static str>,
104 templates: HashMap<&'static str, &'t str>,
105 pub full_width: bool,
106 pub max_width: Option<usize>,
107}
108
109impl<'t> Printer<'t> {
110 pub fn new(mut cmd: Command) -> Self {
111 cmd.build();
112 let expander = Self::make_expander(&cmd);
113 let mut templates = HashMap::new();
114 templates.insert("title", TEMPLATE_TITLE);
115 templates.insert("author", TEMPLATE_AUTHOR);
116 templates.insert("usage", TEMPLATE_USAGE);
117 templates.insert("positionals", TEMPLATE_POSITIONALS);
118 templates.insert("options", TEMPLATE_OPTIONS);
119 Self {
120 skin: Self::make_skin(),
121 expander,
122 templates,
123 template_keys: TEMPLATES.to_vec(),
124 full_width: false,
125 max_width: None,
126 }
127 }
128 pub fn make_skin() -> MadSkin {
131 match terminal_light::luma() {
132 Ok(luma) if luma > 0.85 => MadSkin::default_light(),
133 Ok(luma) if luma < 0.2 => MadSkin::default_dark(),
134 _ => MadSkin::default(),
135 }
136 }
137 pub fn with_skin(mut self, skin: MadSkin) -> Self {
139 self.skin = skin;
140 self
141 }
142 pub fn with_max_width(mut self, w: usize) -> Self {
149 self.max_width = Some(w);
150 self
151 }
152 pub fn skin_mut(&mut self) -> &mut MadSkin {
156 &mut self.skin
157 }
158 pub fn set_template(&mut self, key: &'static str, template: &'t str) {
160 self.templates.insert(key, template);
161 }
162 pub fn with(mut self, key: &'static str, template: &'t str) -> Self {
164 self.set_template(key, template);
165 self
166 }
167 pub fn without(mut self, key: &'static str) -> Self {
169 self.templates.remove(key);
170 self
171 }
172 pub fn template_keys_mut(&mut self) -> &mut Vec<&'static str> {
176 &mut self.template_keys
177 }
178 #[deprecated(since = "0.6.2", note = "use template_keys_mut instead")]
182 pub fn template_order_mut(&mut self) -> &mut Vec<&'static str> {
183 &mut self.template_keys
184 }
185 fn make_expander(cmd: &Command) -> OwningTemplateExpander<'static> {
186 let mut expander = OwningTemplateExpander::new();
187 expander.set_default("");
188 let name = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name());
189 expander.set("name", name);
190 if let Some(author) = cmd.get_author() {
191 expander.set("author", author);
192 }
193 if let Some(version) = cmd.get_version() {
194 expander.set("version", version);
195 }
196 let options = cmd
197 .get_arguments()
198 .filter(|a| !a.is_hide_set())
199 .filter(|a| a.get_short().is_some() || a.get_long().is_some());
200 for arg in options {
201 let sub = expander.sub("option-lines");
202 if let Some(short) = arg.get_short() {
203 sub.set("short", format!("-{short}"));
204 }
205 if let Some(long) = arg.get_long() {
206 sub.set("long", format!("--{long}"));
207 }
208 if let Some(help) = arg.get_help() {
209 sub.set_md("help", help.to_string());
210 }
211 if arg.get_action().takes_values() {
212 if let Some(name) = arg.get_value_names().and_then(|arr| arr.first()) {
213 sub.set("value", name);
214 let braced = format!("<{}>", name);
215 sub.set("value-braced", &braced);
216 if arg.get_short().is_some() {
217 sub.set("value-short-braced", &braced);
218 sub.set("value-short", name);
219 }
220 if arg.get_long().is_some() {
221 sub.set("value-long-braced", &braced);
222 sub.set("value-long", name);
223 }
224 };
225 }
226 let mut possible_values = arg.get_possible_values();
227 if !possible_values.is_empty() {
228 let possible_values: Vec<String> = possible_values
229 .drain(..)
230 .map(|v| format!("`{}`", v.get_name()))
231 .collect();
232 expander.sub("option-lines").set_md(
233 "possible_values",
234 format!(" Possible values: [{}]", possible_values.join(", ")),
235 );
236 }
237 if let Some(default) = arg.get_default_values().first() {
238 match arg.get_action() {
239 ArgAction::Set | ArgAction::Append => {
240 expander.sub("option-lines").set_md(
241 "default",
242 format!(" Default: `{}`", default.to_string_lossy()),
243 );
244 }
245 _ => {}
246 }
247 }
248 }
249 let mut args = String::new();
250 for arg in cmd.get_positionals() {
251 let Some(key) = arg.get_value_names().and_then(|arr| arr.first()) else {
252 continue;
253 };
254 args.push(' ');
255 if !arg.is_required_set() {
256 args.push('[');
257 }
258 if arg.is_last_set() {
259 args.push_str("-- ");
260 }
261 args.push_str(key);
262 if !arg.is_required_set() {
263 args.push(']');
264 }
265 let sub = expander.sub("positional-lines");
266 sub.set("key", key);
267 if let Some(help) = arg.get_help() {
268 sub.set("help", help);
269 }
270 }
271 expander.set("positional-args", args);
272 expander
273 }
274 pub fn expander_mut(&mut self) -> &mut OwningTemplateExpander<'static> {
278 &mut self.expander
279 }
280 pub fn print_template(&self, template: &str) {
285 self.skin.print_owning_expander_md(&self.expander, template);
286 }
287 pub fn print_help(&self) {
289 if self.full_width {
290 self.print_help_full_width()
291 } else {
292 self.print_help_content_width()
293 }
294 }
295 fn print_help_full_width(&self) {
296 for key in &self.template_keys {
297 if let Some(template) = self.templates.get(key) {
298 self.print_template(template);
299 }
300 }
301 }
302 fn print_help_content_width(&self) {
303 let (width, _) = termimad::terminal_size();
304 let mut width = width as usize;
305 if let Some(max_width) = self.max_width {
306 width = width.min(max_width);
307 }
308 let mut texts: Vec<FmtText> = self
309 .template_keys
310 .iter()
311 .filter_map(|key| self.templates.get(key))
312 .map(|&template| {
313 let template = TextTemplate::from(template);
314 let text = self.expander.expand(&template);
315 FmtText::from_text(&self.skin, text, Some(width))
316 })
317 .collect();
318 let content_width = texts
319 .iter()
320 .fold(0, |cw, text| cw.max(text.content_width()));
321 for text in &mut texts {
322 text.set_rendering_width(content_width);
323 println!("{}", text);
324 }
325 }
326}