1use {
2 clap::{ArgAction, Command},
3 std::{
4 collections::HashMap,
5 io::{self, Write},
6 },
7 termimad::{
8 minimad::{OwningTemplateExpander, TextTemplate},
9 FmtText, MadSkin,
10 },
11};
12
13pub static TEMPLATE_TITLE: &str = "# **${name}** ${version}";
15
16pub static TEMPLATE_AUTHOR: &str = "
18*by* ${author}
19";
20
21pub static TEMPLATE_USAGE: &str = "
23**Usage: ** `${name} [options]${positional-args}`
24";
25
26pub static TEMPLATE_POSITIONALS: &str = "
28${positional-lines
29* `${key}` : ${help}
30}
31";
32
33pub static TEMPLATE_OPTIONS: &str = "
35**Options:**
36|:-:|:-:|:-:|:-|
37|short|long|value|description|
38|:-:|:-|:-:|:-|
39${option-lines
40|${short}|${long}|${value}|${help}${possible_values}${default}|
41}
42|-
43";
44
45pub static TEMPLATE_OPTIONS_MERGED_VALUE: &str = "
47**Options:**
48|:-:|:-:|:-|
49|short|long|description|
50|:-:|:-|:-|
51${option-lines
52|${short} *${value-short-braced}*|${long} *${value-long-braced}*|${help}${possible_values}${default}|
53}
54|-
55";
56
57pub static TEMPLATES: &[&str] = &[
59 "title",
60 "author",
61 "introduction",
62 "usage",
63 "positionals",
64 "options",
65 "bugs",
66];
67
68pub struct Printer<'t> {
104 skin: MadSkin,
105 expander: OwningTemplateExpander<'static>,
106 template_keys: Vec<&'static str>,
107 templates: HashMap<&'static str, &'t str>,
108 pub full_width: bool,
109 pub max_width: Option<usize>,
110}
111
112impl<'t> Printer<'t> {
113 pub fn new(mut cmd: Command) -> Self {
114 cmd.build();
115 let expander = Self::make_expander(&cmd);
116 let mut templates = HashMap::new();
117 templates.insert("title", TEMPLATE_TITLE);
118 templates.insert("author", TEMPLATE_AUTHOR);
119 templates.insert("usage", TEMPLATE_USAGE);
120 templates.insert("positionals", TEMPLATE_POSITIONALS);
121 templates.insert("options", TEMPLATE_OPTIONS);
122 Self {
123 skin: Self::make_skin(),
124 expander,
125 templates,
126 template_keys: TEMPLATES.to_vec(),
127 full_width: false,
128 max_width: None,
129 }
130 }
131 pub fn make_skin() -> MadSkin {
134 match terminal_light::luma() {
135 Ok(luma) if luma > 0.85 => MadSkin::default_light(),
136 Ok(luma) if luma < 0.2 => MadSkin::default_dark(),
137 _ => MadSkin::default(),
138 }
139 }
140 pub fn with_skin(mut self, skin: MadSkin) -> Self {
142 self.skin = skin;
143 self
144 }
145 pub fn with_max_width(mut self, w: usize) -> Self {
152 self.max_width = Some(w);
153 self
154 }
155 pub fn skin_mut(&mut self) -> &mut MadSkin {
159 &mut self.skin
160 }
161 pub fn set_template(&mut self, key: &'static str, template: &'t str) {
163 self.templates.insert(key, template);
164 }
165 pub fn with(mut self, key: &'static str, template: &'t str) -> Self {
167 self.set_template(key, template);
168 self
169 }
170 pub fn without(mut self, key: &'static str) -> Self {
172 self.templates.remove(key);
173 self
174 }
175 pub fn template_keys_mut(&mut self) -> &mut Vec<&'static str> {
179 &mut self.template_keys
180 }
181 #[deprecated(since = "0.6.2", note = "use template_keys_mut instead")]
185 pub fn template_order_mut(&mut self) -> &mut Vec<&'static str> {
186 &mut self.template_keys
187 }
188 fn make_expander(cmd: &Command) -> OwningTemplateExpander<'static> {
189 let mut expander = OwningTemplateExpander::new();
190 expander.set_default("");
191 let name = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name());
192 expander.set("name", name);
193 if let Some(author) = cmd.get_author() {
194 expander.set("author", author);
195 }
196 if let Some(version) = cmd.get_version() {
197 expander.set("version", version);
198 }
199 let options = cmd
200 .get_arguments()
201 .filter(|a| !a.is_hide_set())
202 .filter(|a| a.get_short().is_some() || a.get_long().is_some());
203 for arg in options {
204 let sub = expander.sub("option-lines");
205 if let Some(short) = arg.get_short() {
206 sub.set("short", format!("-{short}"));
207 }
208 if let Some(long) = arg.get_long() {
209 sub.set("long", format!("--{long}"));
210 }
211 if let Some(help) = arg.get_help() {
212 sub.set_md("help", help.to_string());
213 }
214 if arg.get_action().takes_values() {
215 if let Some(name) = arg.get_value_names().and_then(|arr| arr.first()) {
216 sub.set("value", name);
217 let braced = format!("<{name}>");
218 sub.set("value-braced", &braced);
219 if arg.get_short().is_some() {
220 sub.set("value-short-braced", &braced);
221 sub.set("value-short", name);
222 }
223 if arg.get_long().is_some() {
224 sub.set("value-long-braced", &braced);
225 sub.set("value-long", name);
226 }
227 }
228 }
229 let mut possible_values = arg.get_possible_values();
230 if !possible_values.is_empty() {
231 let possible_values: Vec<String> = possible_values
232 .drain(..)
233 .map(|v| format!("`{}`", v.get_name()))
234 .collect();
235 expander.sub("option-lines").set_md(
236 "possible_values",
237 format!(" Possible values: [{}]", possible_values.join(", ")),
238 );
239 }
240 if let Some(default) = arg.get_default_values().first() {
241 match arg.get_action() {
242 ArgAction::Set | ArgAction::Append => {
243 expander.sub("option-lines").set_md(
244 "default",
245 format!(" Default: `{}`", default.to_string_lossy()),
246 );
247 }
248 _ => {}
249 }
250 }
251 }
252 let mut args = String::new();
253 for arg in cmd.get_positionals() {
254 let Some(key) = arg.get_value_names().and_then(|arr| arr.first()) else {
255 continue;
256 };
257 args.push(' ');
258 if !arg.is_required_set() {
259 args.push('[');
260 }
261 if arg.is_last_set() {
262 args.push_str("-- ");
263 }
264 args.push_str(key);
265 if !arg.is_required_set() {
266 args.push(']');
267 }
268 let sub = expander.sub("positional-lines");
269 sub.set("key", key);
270 if let Some(help) = arg.get_help() {
271 sub.set("help", help);
272 }
273 }
274 expander.set("positional-args", args);
275 expander
276 }
277 pub fn expander_mut(&mut self) -> &mut OwningTemplateExpander<'static> {
281 &mut self.expander
282 }
283 pub fn print_template(&self, template: &str) {
288 self.skin.print_owning_expander_md(&self.expander, template);
289 }
290 pub fn write_template<W: Write>(&self, w: &mut W, template: &str) -> io::Result<()> {
295 self.skin.write_owning_expander_md(w, &self.expander, template)
296 }
297 pub fn print_help(&self) {
302 self.write_help(&mut io::stdout())
303 .expect("failed printing to stdout");
304 }
305 pub fn write_help<W: Write>(&self, w: &mut W) -> io::Result<()> {
307 if self.full_width {
308 self.write_help_full_width(w)
309 } else {
310 self.write_help_content_width(w)
311 }
312 }
313 fn write_help_full_width<W: Write>(&self, w: &mut W) -> io::Result<()> {
314 for key in &self.template_keys {
315 if let Some(template) = self.templates.get(key) {
316 self.write_template(w, template)?;
317 }
318 }
319 Ok(())
320 }
321 fn write_help_content_width<W: Write>(&self, w: &mut W) -> io::Result<()> {
322 let (width, _) = termimad::terminal_size();
323 let mut width = width as usize;
324 if let Some(max_width) = self.max_width {
325 width = width.min(max_width);
326 }
327 let mut texts: Vec<FmtText> = self
328 .template_keys
329 .iter()
330 .filter_map(|key| self.templates.get(key))
331 .map(|&template| {
332 let template = TextTemplate::from(template);
333 let text = self.expander.expand(&template);
334 FmtText::from_text(&self.skin, text, Some(width))
335 })
336 .collect();
337 let content_width = texts
338 .iter()
339 .fold(0, |cw, text| cw.max(text.content_width()));
340 for text in &mut texts {
341 text.set_rendering_width(content_width);
342 writeln!(w, "{text}")?;
343 }
344 Ok(())
345 }
346}