aopt_help/format/
policy.rs

1use std::borrow::Cow;
2use std::io::Write;
3use std::marker::PhantomData;
4
5use crate::block::Block;
6use crate::cmd::Command;
7use crate::store::Store;
8use crate::style::Style;
9use crate::wrapper::Wrapper;
10use crate::AppHelp;
11use crate::HelpPolicy;
12
13// struct UsageDetail<'a> {
14//     store_usages: Vec<Cow<'a, str>>,
15
16//     args: Vec<Cow<'a, str>>,
17// }
18
19pub struct DefaultPolicy<'a, I> {
20    name: Cow<'a, str>,
21
22    style: Style,
23
24    styles: Vec<Style>,
25
26    max_width: usize,
27
28    hiding_pos: bool,
29
30    usage_new_line: usize,
31
32    marker: PhantomData<&'a I>,
33}
34
35impl<I> Default for DefaultPolicy<'_, I> {
36    fn default() -> Self {
37        Self {
38            name: Default::default(),
39            style: Default::default(),
40            styles: Default::default(),
41            max_width: 0,
42            hiding_pos: true,
43            usage_new_line: 0,
44            marker: Default::default(),
45        }
46    }
47}
48
49impl<'a, I> DefaultPolicy<'a, I> {
50    pub fn new<S: Into<Cow<'a, str>>>(
51        name: S,
52        style: Style,
53        block: Vec<Style>,
54        max_width: usize,
55        hiding_pos: bool,
56        usage_new_line: usize,
57    ) -> Self {
58        Self {
59            name: name.into(),
60            style,
61            styles: block,
62            max_width,
63            hiding_pos,
64            usage_new_line,
65            marker: PhantomData,
66        }
67    }
68}
69
70impl<'a> DefaultPolicy<'a, Command<'a>> {
71    pub fn get_block_usage(
72        &self,
73        item: &Block<'a, Cow<'a, str>>,
74        stores: &[Store<'a>],
75    ) -> (Vec<String>, Vec<String>) {
76        let mut usages = vec![];
77        let mut args = vec![];
78
79        for store in item.iter() {
80            if let Some(store) = stores.iter().find(|v| &v.name() == store) {
81                let hint = store.hint();
82
83                if !hint.is_empty() {
84                    if store.position() {
85                        if store.optional() {
86                            args.push(format!("[{}]", hint));
87                        } else {
88                            args.push(format!("<{}>", hint));
89                        }
90                    } else if store.optional() {
91                        usages.push(format!("[{}]", hint));
92                    } else {
93                        usages.push(format!("<{}>", hint));
94                    }
95                }
96            }
97        }
98        (usages, args)
99    }
100
101    pub fn get_command_usage(&self, item: &Command<'a>) -> Cow<'a, str> {
102        let mut usages = vec![];
103        let mut args = vec![];
104        let mut block_hint = vec![];
105
106        for block in item.block() {
107            let (block_usages, mut block_args) = self.get_block_usage(block, item);
108
109            if !block_usages.is_empty() {
110                for mut usage in block_usages {
111                    if self.usage_new_line > 0 && (usages.len() + 1) % self.usage_new_line == 0 {
112                        // add more space
113                        // same length as `Usage: `
114                        usage.push_str("\n      ");
115                    }
116                    usages.push(usage);
117                }
118            }
119            // if not omit args, using the args, otherwise using hint of block
120            if !block_args.is_empty() {
121                args.append(&mut block_args);
122            }
123        }
124        for block in item.block() {
125            if !block.is_empty() {
126                let arg = block.hint();
127
128                if !arg.is_empty() {
129                    block_hint.push(arg);
130                }
131            }
132        }
133
134        let mut ret = String::from("Usage: ");
135        let usage = usages.join(" ");
136        let block_hint = block_hint.join(" ");
137        let args = args.join(" ");
138
139        if !self.name.is_empty() {
140            ret += &self.name;
141            ret += " ";
142        }
143        if !item.name().is_empty() {
144            ret += &item.name();
145            ret += " ";
146        }
147        if !usage.is_empty() {
148            ret += &usage;
149            ret += " ";
150        }
151        if self.hiding_pos {
152            if !block_hint.is_empty() {
153                ret += &block_hint;
154                ret += " ";
155            }
156        } else if !args.is_empty() {
157            ret += &args;
158            ret += " ";
159        }
160        ret.into()
161    }
162
163    pub fn get_block_help(
164        &self,
165        item: &Block<'a, Cow<'a, str>>,
166        stores: &Vec<Store<'a>>,
167    ) -> Cow<'a, str> {
168        let style = &self.style;
169        let count = item.len();
170        let head = item.head();
171        let foot = item.foot();
172        let line_spacing = &"\n".repeat(1 + style.line_spacing);
173        let mut output = if head.is_empty() { vec![] } else { vec![head] };
174        let mut data: Vec<Vec<Cow<'a, str>>> = vec![vec![]; count];
175        let blocks = item.as_slice();
176        let styles = &self.styles;
177        let mut any_filled = false;
178
179        for idx in 0..count {
180            for store in stores {
181                if store.name() == blocks[idx] {
182                    let hint = store.hint();
183                    let help = store.help();
184
185                    if !hint.is_empty() {
186                        data[idx].push(hint);
187                        any_filled = true;
188                    }
189                    if !help.is_empty() {
190                        data[idx].push(help);
191                        any_filled = true;
192                    }
193                }
194            }
195        }
196        if !any_filled {
197            return "".into();
198        }
199        let mut wrapper = Wrapper::new(&data);
200
201        if !styles.is_empty() {
202            wrapper.wrap_with(styles, self.max_width);
203        } else {
204            wrapper.wrap(self.max_width);
205        }
206        let wrapped = wrapper.get_output();
207        let mut wrapped_lines = vec![];
208
209        for wrapped_line in wrapped {
210            let max_len = wrapped_line.iter().map(|v| v.len()).max().unwrap_or(1);
211            let first_style = wrapped_line[0].get_style();
212            let first_row_spacing = &" ".repeat(first_style.row_spacing);
213            let first_line_spacing = &"\n".repeat(1 + first_style.line_spacing);
214
215            for i in 0..max_len {
216                let rows = &wrapped_line
217                    .iter()
218                    .map(|v| v.get_line(i))
219                    .collect::<Vec<String>>();
220                let mut line = rows.join(first_row_spacing);
221
222                line.push_str(first_line_spacing);
223                wrapped_lines.push(line);
224            }
225        }
226        let wrapped_output = wrapped_lines.join("");
227
228        if !wrapped_output.is_empty() {
229            output.push(wrapped_output.into());
230        }
231        if !foot.is_empty() {
232            output.push(foot);
233        }
234        let output = output.join(line_spacing);
235        let output = output.trim_end().to_owned();
236
237        output.into()
238    }
239}
240
241impl<'a> HelpPolicy<'a, Command<'a>> for DefaultPolicy<'a, Command<'a>> {
242    fn format(&self, item: &Command<'a>) -> Option<Cow<'a, str>> {
243        let usage = self.get_command_usage(item);
244        let mut blocks = vec![usage];
245        let head = item.head();
246        let foot = item.foot();
247        let block_spacing = "\n".repeat(1 + self.style.block_spacing);
248
249        if !head.is_empty() {
250            blocks.push(head);
251        }
252        for block in item.block() {
253            if !block.is_empty() {
254                let help = self.get_block_help(block, item);
255
256                if !help.is_empty() {
257                    blocks.push(help);
258                }
259            }
260        }
261        if !foot.is_empty() {
262            blocks.push(foot);
263        }
264        Some(blocks.join(&block_spacing).into())
265    }
266}
267
268pub struct DefaultAppPolicy<'a, I> {
269    styles: Vec<Style>, // style for every block
270
271    max_width: usize,
272
273    show_global: bool,
274
275    hiding_pos: bool,
276
277    usage_new_line: usize,
278
279    marker: PhantomData<&'a I>,
280}
281
282impl<I> Default for DefaultAppPolicy<'_, I> {
283    fn default() -> Self {
284        Self {
285            styles: Default::default(),
286            max_width: 0,
287            show_global: true,
288            hiding_pos: true,
289            usage_new_line: 0,
290            marker: Default::default(),
291        }
292    }
293}
294
295impl<I> DefaultAppPolicy<'_, I> {
296    pub fn new(
297        styles: Vec<Style>,
298        max_width: usize,
299        show_global: bool,
300        usage_new_line: usize,
301    ) -> Self {
302        Self {
303            styles,
304            max_width,
305            show_global,
306            hiding_pos: true,
307            usage_new_line,
308            marker: PhantomData,
309        }
310    }
311}
312
313impl<'a, W: Write> DefaultAppPolicy<'a, AppHelp<'a, W>> {
314    pub fn get_block_usage(
315        &self,
316        item: &Block<'a, Cow<'a, str>>,
317        stores: &[Store<'a>],
318    ) -> (Vec<String>, Vec<String>) {
319        let mut usages = vec![];
320        let mut args = vec![];
321
322        for store in item.iter() {
323            if let Some(store) = stores.iter().find(|v| &v.name() == store) {
324                let hint = store.hint();
325
326                if !hint.is_empty() {
327                    if store.position() {
328                        if store.optional() {
329                            args.push(format!("[{}]", hint));
330                        } else {
331                            args.push(format!("<{}>", hint));
332                        }
333                    } else if store.optional() {
334                        usages.push(format!("[{}]", hint));
335                    } else {
336                        usages.push(format!("<{}>", hint));
337                    }
338                }
339            }
340        }
341        (usages, args)
342    }
343
344    pub fn get_app_usage(&self, app: &AppHelp<'a, W>) -> Cow<'a, str> {
345        let global = app.global();
346        let mut usages = vec![];
347        let mut args = vec![];
348        let mut block_hint = vec![];
349
350        for block in global.block() {
351            let (block_usages, mut block_args) = self.get_block_usage(block, global);
352
353            if !block_usages.is_empty() {
354                for mut usage in block_usages {
355                    if self.usage_new_line > 0 && (usages.len() + 1) % self.usage_new_line == 0 {
356                        // add more space
357                        // same length as `Usage: `
358                        usage.push_str("\n      ");
359                    }
360                    usages.push(usage);
361                }
362            }
363            // if not omit args, using the args, otherwise using hint of block
364            if !block_args.is_empty() {
365                args.append(&mut block_args);
366            }
367        }
368        for block in global.block() {
369            if !block.is_empty() {
370                let arg = block.hint();
371
372                if !arg.is_empty() {
373                    block_hint.push(arg);
374                }
375            }
376        }
377
378        let mut ret = String::from("Usage: ");
379        // all the option usage
380        let global_usage = usages.join(" ");
381        let block_hint = block_hint.join(" ");
382        let command_usage = if app.has_cmd() { "<COMMAND>" } else { "" };
383        let args = args.join(" ");
384
385        if !global.name().is_empty() {
386            ret += &global.name();
387            ret += " ";
388        }
389        if !global_usage.is_empty() {
390            ret += &global_usage;
391            ret += " ";
392        }
393        if !command_usage.is_empty() {
394            ret += command_usage;
395            ret += " ";
396        }
397        if self.hiding_pos {
398            if !block_hint.is_empty() {
399                ret += &block_hint;
400                ret += " ";
401            }
402        } else if !args.is_empty() {
403            ret += &args;
404            ret += " ";
405        }
406        ret.into()
407    }
408
409    pub fn get_block_help(
410        &self,
411        block: &Block<'a, Cow<'a, str>>,
412        app: &AppHelp<'a, W>,
413    ) -> Cow<'a, str> {
414        let head = block.head();
415        let foot = block.foot();
416        let count = block.len();
417        let mut data = vec![vec![]; count];
418        let styles = &self.styles;
419        let line_spacing = &"\n".repeat(1 + app.style().line_spacing);
420        let mut any_filled = false;
421
422        if block.is_empty() {
423            return "".into();
424        }
425        for (name, data_mut) in block.iter().zip(data.iter_mut()) {
426            if let Some(command) = app.find_cmd(name.clone()) {
427                let hint = command.hint();
428                let help = command.help();
429
430                if !hint.is_empty() {
431                    data_mut.push(hint);
432                    any_filled = true;
433                }
434                if !help.is_empty() {
435                    data_mut.push(help);
436                    any_filled = true;
437                }
438            } else {
439                panic!("Unknow command {} in block {}", name, block.name());
440            }
441        }
442        if !any_filled {
443            return "".into();
444        }
445        let mut wrapper = Wrapper::new(&data);
446
447        if !styles.is_empty() {
448            wrapper.wrap_with(styles, self.max_width);
449        } else {
450            wrapper.wrap(self.max_width);
451        }
452        let wrapped = wrapper.get_output();
453        let mut wrapped_lines = vec![];
454
455        for wrapped_line in wrapped {
456            let max_len = wrapped_line.iter().map(|v| v.len()).max().unwrap_or(1);
457            let first_style = wrapped_line[0].get_style();
458            let first_row_spacing = &" ".repeat(first_style.row_spacing);
459            let first_line_spacing = &"\n".repeat(1 + first_style.line_spacing);
460
461            for i in 0..max_len {
462                let rows = &wrapped_line
463                    .iter()
464                    .map(|v| v.get_line(i))
465                    .collect::<Vec<String>>();
466                let mut line = rows.join(first_row_spacing);
467
468                line.push_str(first_line_spacing);
469                wrapped_lines.push(line);
470            }
471        }
472
473        if !wrapped_lines.is_empty() {
474            // pop last line spacing
475            wrapped_lines.last_mut().unwrap().pop();
476        }
477
478        let mut usages = vec![];
479        let wrapped_output = wrapped_lines.join("");
480
481        if !head.is_empty() {
482            usages.push(head);
483        }
484        if !foot.is_empty() {
485            usages.push(foot);
486        }
487        if !wrapped_output.is_empty() {
488            usages.push(wrapped_output.into());
489        }
490
491        usages.join(line_spacing).into()
492    }
493
494    pub fn get_global_help(
495        &self,
496        item: &Block<'a, Cow<'a, str>>,
497        stores: &Vec<Store<'a>>,
498        app: &AppHelp<'a, W>,
499    ) -> Cow<'a, str> {
500        let style = &app.style;
501        let count = item.len();
502        let head = item.head();
503        let foot = item.foot();
504        let line_spacing = &"\n".repeat(1 + style.line_spacing);
505        let mut output = if head.is_empty() { vec![] } else { vec![head] };
506        let mut data: Vec<Vec<Cow<'a, str>>> = vec![vec![]; count];
507        let blocks = item.as_slice();
508        let styles = &self.styles;
509        let mut any_filled = false;
510
511        if item.is_empty() {
512            return "".into();
513        }
514        for idx in 0..count {
515            for store in stores {
516                if store.name() == blocks[idx] {
517                    let hint = store.hint();
518                    let help = store.help();
519
520                    if !hint.is_empty() {
521                        data[idx].push(hint);
522                        any_filled = true;
523                    }
524                    if !help.is_empty() {
525                        data[idx].push(help);
526                        any_filled = true;
527                    }
528                }
529            }
530        }
531        if !any_filled {
532            return "".into();
533        }
534        let mut wrapper = Wrapper::new(&data);
535
536        if !styles.is_empty() {
537            wrapper.wrap_with(styles, self.max_width);
538        } else {
539            wrapper.wrap(self.max_width);
540        }
541        let wrapped = wrapper.get_output();
542        let mut wrapped_lines = vec![];
543
544        for wrapped_line in wrapped {
545            let max_len = wrapped_line.iter().map(|v| v.len()).max().unwrap_or(1);
546            let first_style = wrapped_line[0].get_style();
547            let first_row_spacing = &" ".repeat(first_style.row_spacing);
548            let first_line_spacing = &"\n".repeat(1 + first_style.line_spacing);
549
550            for i in 0..max_len {
551                let rows = &wrapped_line
552                    .iter()
553                    .map(|v| v.get_line(i))
554                    .collect::<Vec<String>>();
555                let mut line = rows.join(first_row_spacing);
556
557                line.push_str(first_line_spacing);
558                wrapped_lines.push(line);
559            }
560        }
561        let wrapped_output = wrapped_lines.join("");
562
563        if !wrapped_output.is_empty() {
564            output.push(wrapped_output.into());
565        }
566        if !foot.is_empty() {
567            output.push(foot);
568        }
569        let output = output.join(line_spacing);
570        let output = output.trim_end().to_owned();
571
572        output.into()
573    }
574}
575
576impl<'a, W: Write> HelpPolicy<'a, AppHelp<'a, W>> for DefaultAppPolicy<'a, AppHelp<'a, W>> {
577    fn format(&self, app: &AppHelp<'a, W>) -> Option<Cow<'a, str>> {
578        let usage = self.get_app_usage(app);
579        let head = app.head();
580        let foot = app.foot();
581        let block_spacing = "\n".repeat(1 + app.style().block_spacing);
582        let mut usages = if usage.is_empty() {
583            vec![]
584        } else {
585            vec![usage]
586        };
587
588        if !head.is_empty() {
589            usages.push(head);
590        }
591        for block in app.block().iter() {
592            let block_help = self.get_block_help(block, app);
593
594            if !block_help.is_empty() {
595                usages.push(block_help);
596            }
597        }
598        if self.show_global {
599            for block in app.global().block() {
600                let global_help = self.get_global_help(block, app.global(), app);
601
602                if !global_help.is_empty() {
603                    usages.push(global_help);
604                }
605            }
606        }
607        if !foot.is_empty() {
608            usages.push(foot);
609        }
610        Some(usages.join(&block_spacing).into())
611    }
612}