1use std::collections::BTreeSet;
2
3use crate::{
4 buffer::{Block, Doc, Style, Token},
5 info::Info,
6 item::{Item, ShortLong},
7 Meta,
8};
9
10#[doc(hidden)]
11#[derive(Debug, Clone, Copy)]
12pub struct Metavar(pub(crate) &'static str);
13
14#[derive(Debug, Clone, Copy)]
15pub(crate) enum HelpItem<'a> {
16 DecorSuffix {
17 help: &'a Doc,
18 ty: HiTy,
19 },
20 GroupStart {
21 help: &'a Doc,
22 ty: HiTy,
23 },
24 GroupEnd {
25 ty: HiTy,
26 },
27 Any {
28 metavar: &'a Doc,
29 anywhere: bool,
30 help: Option<&'a Doc>,
31 },
32 Positional {
33 metavar: Metavar,
34 help: Option<&'a Doc>,
35 },
36 Command {
37 name: &'static str,
38 short: Option<char>,
39 help: Option<&'a Doc>,
40 meta: &'a Meta,
41 #[cfg(feature = "docgen")]
42 info: &'a Info,
43 },
44 Flag {
45 name: ShortLong,
46 env: Option<&'static str>,
47 help: Option<&'a Doc>,
48 },
49 Argument {
50 name: ShortLong,
51 metavar: Metavar,
52 env: Option<&'static str>,
53 help: Option<&'a Doc>,
54 },
55 AnywhereStart {
56 inner: &'a Meta,
57 ty: HiTy,
58 },
59 AnywhereStop {
60 ty: HiTy,
61 },
62}
63impl HelpItem<'_> {
64 fn has_help(&self) -> bool {
65 match self {
66 HelpItem::Positional { help, .. }
67 | HelpItem::Command { help, .. }
68 | HelpItem::Flag { help, .. }
69 | HelpItem::Any { help, .. }
70 | HelpItem::Argument { help, .. } => help.is_some(),
71 HelpItem::GroupStart { .. } | HelpItem::DecorSuffix { .. } => true,
72 HelpItem::GroupEnd { .. }
73 | HelpItem::AnywhereStart { .. }
74 | HelpItem::AnywhereStop { .. } => false,
75 }
76 }
77
78 fn ty(&self) -> HiTy {
79 match self {
80 HelpItem::GroupStart { ty, .. }
81 | HelpItem::DecorSuffix { ty, .. }
82 | HelpItem::GroupEnd { ty }
83 | HelpItem::AnywhereStart { ty, .. }
84 | HelpItem::AnywhereStop { ty } => *ty,
85 HelpItem::Any {
86 anywhere: false, ..
87 }
88 | HelpItem::Positional { .. } => HiTy::Positional,
89 HelpItem::Command { .. } => HiTy::Command,
90 HelpItem::Any { anywhere: true, .. }
91 | HelpItem::Flag { .. }
92 | HelpItem::Argument { .. } => HiTy::Flag,
93 }
94 }
95}
96
97#[derive(Default, Debug)]
98pub(crate) struct HelpItems<'a> {
102 pub(crate) items: Vec<HelpItem<'a>>,
103}
104
105#[derive(Copy, Clone, Eq, PartialEq, Debug)]
106pub(crate) enum HiTy {
107 Flag,
108 Command,
109 Positional,
110}
111
112enum ItemBlock {
113 No,
114 Decor(HiTy),
115 Anywhere(HiTy),
116}
117
118pub(crate) struct HelpItemsIter<'a, 'b> {
119 items: &'b [HelpItem<'a>],
120 target: HiTy,
121 cur: usize,
122 block: ItemBlock,
123}
124
125impl<'a, 'b> Iterator for HelpItemsIter<'a, 'b> {
126 type Item = &'b HelpItem<'a>;
127
128 fn next(&mut self) -> Option<Self::Item> {
129 loop {
130 let item = self.items.get(self.cur)?;
131 self.cur += 1;
132
133 let keep = match item {
134 HelpItem::AnywhereStart { ty, .. } => {
135 self.block = ItemBlock::Anywhere(*ty);
136 *ty == self.target
137 }
138 HelpItem::GroupStart { ty, .. } => {
139 self.block = ItemBlock::Decor(*ty);
140 *ty == self.target
141 }
142 HelpItem::GroupEnd { ty, .. } | HelpItem::AnywhereStop { ty, .. } => {
143 self.block = ItemBlock::No;
144 *ty == self.target
145 }
146 HelpItem::DecorSuffix { .. }
147 | HelpItem::Any { .. }
148 | HelpItem::Command { .. }
149 | HelpItem::Positional { .. }
150 | HelpItem::Flag { .. }
151 | HelpItem::Argument { .. } => {
152 let ty = item.ty();
153 match self.block {
154 ItemBlock::No => ty == self.target,
155 ItemBlock::Decor(t) => t == self.target,
156 ItemBlock::Anywhere(t) => t == self.target && item.has_help(),
157 }
158 }
159 };
160 if keep {
161 return Some(item);
162 }
163 }
164 }
165}
166
167impl<'a> HelpItems<'a> {
168 #[inline(never)]
169 fn items_of_ty(&self, target: HiTy) -> impl Iterator<Item = &HelpItem> {
170 HelpItemsIter {
171 items: &self.items,
172 target,
173 cur: 0,
174 block: ItemBlock::No,
175 }
176 }
177}
178
179impl Meta {
180 fn peek_front_ty(&self) -> Option<HiTy> {
181 match self {
182 Meta::And(xs) | Meta::Or(xs) => xs.iter().find_map(Meta::peek_front_ty),
183 Meta::Optional(x)
184 | Meta::Required(x)
185 | Meta::Adjacent(x)
186 | Meta::Many(x)
187 | Meta::Subsection(x, _)
188 | Meta::Suffix(x, _)
189 | Meta::Strict(x)
190 | Meta::CustomUsage(x, _) => x.peek_front_ty(),
191 Meta::Item(i) => Some(HiTy::from(i.as_ref())),
192 Meta::Skip => None,
193 }
194 }
195}
196
197impl<'a> HelpItems<'a> {
198 pub(crate) fn append_meta(&mut self, meta: &'a Meta) {
200 fn go<'a>(hi: &mut HelpItems<'a>, meta: &'a Meta, no_ss: bool) {
201 match meta {
202 Meta::And(xs) | Meta::Or(xs) => {
203 for x in xs {
204 go(hi, x, no_ss);
205 }
206 }
207 Meta::Adjacent(m) => {
208 if let Some(ty) = m.peek_front_ty() {
209 hi.items.push(HelpItem::AnywhereStart {
210 inner: m.as_ref(),
211 ty,
212 });
213 go(hi, m, no_ss);
214 hi.items.push(HelpItem::AnywhereStop { ty });
215 }
216 }
217 Meta::CustomUsage(x, _)
218 | Meta::Required(x)
219 | Meta::Optional(x)
220 | Meta::Many(x)
221 | Meta::Strict(x) => go(hi, x, no_ss),
222 Meta::Item(item) => {
223 if matches!(item.as_ref(), Item::Positional { help: None, .. }) {
224 return;
225 }
226 hi.items.push(HelpItem::from(item.as_ref()));
227 }
228 Meta::Subsection(m, help) => {
229 if let Some(ty) = m.peek_front_ty() {
230 if no_ss {
231 go(hi, m, true);
232 } else {
233 hi.items.push(HelpItem::GroupStart { help, ty });
234 go(hi, m, true);
235 hi.items.push(HelpItem::GroupEnd { ty });
236 }
237 }
238 }
239 Meta::Suffix(m, help) => {
240 if let Some(ty) = m.peek_front_ty() {
241 go(hi, m, no_ss);
242 hi.items.push(HelpItem::DecorSuffix { help, ty });
243 }
244 }
245 Meta::Skip => (),
246 }
247 }
248 go(self, meta, false);
249 }
250
251 fn find_group(&self) -> Option<std::ops::RangeInclusive<usize>> {
252 let start = self
253 .items
254 .iter()
255 .position(|i| matches!(i, HelpItem::GroupStart { .. }))?;
256 let end = self
257 .items
258 .iter()
259 .position(|i| matches!(i, HelpItem::GroupEnd { .. }))?;
260 Some(start..=end)
261 }
262}
263
264impl From<&Item> for HiTy {
265 fn from(value: &Item) -> Self {
266 match value {
267 Item::Positional { .. }
268 | Item::Any {
269 anywhere: false, ..
270 } => Self::Positional,
271 Item::Command { .. } => Self::Command,
272 Item::Any { anywhere: true, .. } | Item::Flag { .. } | Item::Argument { .. } => {
273 Self::Flag
274 }
275 }
276 }
277}
278
279impl<'a> From<&'a Item> for HelpItem<'a> {
280 fn from(item: &'a Item) -> Self {
282 match item {
283 Item::Positional { metavar, help } => Self::Positional {
284 metavar: *metavar,
285 help: help.as_ref(),
286 },
287 Item::Command {
288 name,
289 short,
290 help,
291 meta,
292 #[cfg(feature = "docgen")]
293 info,
294 #[cfg(not(feature = "docgen"))]
295 info: _,
296 } => Self::Command {
297 name,
298 short: *short,
299 help: help.as_ref(),
300 meta,
301 #[cfg(feature = "docgen")]
302 info,
303 },
304 Item::Flag {
305 name,
306 env,
307 help,
308 shorts: _,
309 } => Self::Flag {
310 name: *name,
311 env: *env,
312 help: help.as_ref(),
313 },
314 Item::Argument {
315 name,
316 metavar,
317 env,
318 help,
319 shorts: _,
320 } => Self::Argument {
321 name: *name,
322 metavar: *metavar,
323 env: *env,
324 help: help.as_ref(),
325 },
326 Item::Any {
327 metavar,
328 anywhere,
329 help,
330 } => Self::Any {
331 metavar,
332 anywhere: *anywhere,
333 help: help.as_ref(),
334 },
335 }
336 }
337} impl Doc {
340 #[inline(never)]
341 pub(crate) fn metavar(&mut self, metavar: Metavar) {
342 if metavar
343 .0
344 .chars()
345 .all(|c| c.is_uppercase() || c.is_ascii_digit() || c == '-' || c == '_')
346 {
347 self.write_str(metavar.0, Style::Metavar);
348 } else {
349 self.write_char('<', Style::Metavar);
350 self.write_str(metavar.0, Style::Metavar);
351 self.write_char('>', Style::Metavar);
352 }
353 }
354}
355
356#[allow(clippy::too_many_lines)] fn write_help_item(buf: &mut Doc, item: &HelpItem, include_env: bool) {
358 match item {
359 HelpItem::GroupStart { help, .. } => {
360 buf.token(Token::BlockStart(Block::Block));
361 buf.token(Token::BlockStart(Block::Section2));
362 buf.em_doc(help);
363 buf.token(Token::BlockEnd(Block::Section2));
364 buf.token(Token::BlockStart(Block::DefinitionList));
365 }
366 HelpItem::GroupEnd { .. } => {
367 buf.token(Token::BlockEnd(Block::DefinitionList));
368 buf.token(Token::BlockEnd(Block::Block));
369 }
370 HelpItem::DecorSuffix { help, .. } => {
371 buf.token(Token::BlockStart(Block::ItemTerm));
372 buf.token(Token::BlockEnd(Block::ItemTerm));
373 buf.token(Token::BlockStart(Block::ItemBody));
374 buf.doc(help);
375 buf.token(Token::BlockEnd(Block::ItemBody));
376 }
377 HelpItem::Any {
378 metavar,
379 help,
380 anywhere: _,
381 } => {
382 buf.token(Token::BlockStart(Block::ItemTerm));
383 buf.doc(metavar);
384 buf.token(Token::BlockEnd(Block::ItemTerm));
385 if let Some(help) = help {
386 buf.token(Token::BlockStart(Block::ItemBody));
387 buf.doc(help);
388 buf.token(Token::BlockEnd(Block::ItemBody));
389 }
390 }
391 HelpItem::Positional { metavar, help } => {
392 buf.token(Token::BlockStart(Block::ItemTerm));
393 buf.metavar(*metavar);
394 buf.token(Token::BlockEnd(Block::ItemTerm));
395 if let Some(help) = help {
396 buf.token(Token::BlockStart(Block::ItemBody));
397 buf.doc(help);
398 buf.token(Token::BlockEnd(Block::ItemBody));
399 }
400 }
401 HelpItem::Command {
402 name,
403 short,
404 help,
405 meta: _,
406 #[cfg(feature = "docgen")]
407 info: _,
408 } => {
409 buf.token(Token::BlockStart(Block::ItemTerm));
410 buf.write_str(name, Style::Literal);
411 if let Some(short) = short {
412 buf.write_str(", ", Style::Text);
413 buf.write_char(*short, Style::Literal);
414 }
415 buf.token(Token::BlockEnd(Block::ItemTerm));
416 if let Some(help) = help {
417 buf.token(Token::BlockStart(Block::ItemBody));
418 buf.doc(help);
419 buf.token(Token::BlockEnd(Block::ItemBody));
420 }
421 }
422 HelpItem::Flag { name, env, help } => {
423 buf.token(Token::BlockStart(Block::ItemTerm));
424 write_shortlong(buf, *name);
425 buf.token(Token::BlockEnd(Block::ItemTerm));
426 if let Some(help) = help {
427 buf.token(Token::BlockStart(Block::ItemBody));
428 buf.doc(help);
429 buf.token(Token::BlockEnd(Block::ItemBody));
430 }
431 if let Some(env) = env {
432 let val = if std::env::var_os(env).is_some() {
433 ": set"
434 } else {
435 ": not set"
436 };
437 if help.is_some() {
438 buf.token(Token::BlockStart(Block::ItemTerm));
439 buf.token(Token::BlockEnd(Block::ItemTerm));
440 }
441 buf.token(Token::BlockStart(Block::ItemBody));
442 if include_env {
443 buf.write_str(&format!("[env:{}{}]", env, val), Style::Text);
444 } else {
445 buf.text("Uses environment variable ");
446 buf.literal(env);
447 }
448 buf.token(Token::BlockEnd(Block::ItemBody));
449 }
450 }
451 HelpItem::Argument {
452 name,
453 metavar,
454 env,
455 help,
456 } => {
457 buf.token(Token::BlockStart(Block::ItemTerm));
458 write_shortlong(buf, *name);
459 buf.write_str("=", Style::Text);
460 buf.metavar(*metavar);
461 buf.token(Token::BlockEnd(Block::ItemTerm));
462
463 if let Some(help) = help {
464 buf.token(Token::BlockStart(Block::ItemBody));
465 buf.doc(help);
466 buf.token(Token::BlockEnd(Block::ItemBody));
467 }
468
469 if let Some(env) = env {
470 let val = match std::env::var_os(env) {
471 Some(s) => std::borrow::Cow::from(format!(" = {:?}", s.to_string_lossy())),
472 None => std::borrow::Cow::Borrowed(": N/A"),
473 };
474
475 if help.is_some() {
476 buf.token(Token::BlockStart(Block::ItemTerm));
477 buf.token(Token::BlockEnd(Block::ItemTerm));
478 }
479 buf.token(Token::BlockStart(Block::ItemBody));
480
481 if include_env {
482 buf.write_str(&format!("[env:{}{}]", env, val), Style::Text);
483 } else {
484 buf.text("Uses environment variable ");
485 buf.literal(env);
486 }
487
488 buf.token(Token::BlockEnd(Block::ItemBody));
489 }
490 }
491 HelpItem::AnywhereStart { inner, .. } => {
492 buf.token(Token::BlockStart(Block::Section3));
493 buf.write_meta(inner, true);
494 buf.token(Token::BlockEnd(Block::Section3));
495 }
496 HelpItem::AnywhereStop { .. } => {
497 buf.token(Token::BlockStart(Block::Block));
498 buf.token(Token::BlockEnd(Block::Block));
499 }
500 }
501}
502
503fn write_shortlong(buf: &mut Doc, name: ShortLong) {
504 match name {
505 ShortLong::Short(s) => {
506 buf.write_char('-', Style::Literal);
507 buf.write_char(s, Style::Literal);
508 }
509 ShortLong::Long(l) => {
510 buf.write_str(" --", Style::Literal);
511 buf.write_str(l, Style::Literal);
512 }
513 ShortLong::Both(s, l) => {
514 buf.write_char('-', Style::Literal);
515 buf.write_char(s, Style::Literal);
516 buf.write_str(", ", Style::Text);
517 buf.write_str("--", Style::Literal);
518 buf.write_str(l, Style::Literal);
519 }
520 }
521}
522
523#[inline(never)]
524pub(crate) fn render_help(
525 path: &[String],
526 info: &Info,
527 parser_meta: &Meta,
528 help_meta: &Meta,
529 include_env: bool,
530) -> Doc {
531 parser_meta.positional_invariant_check(false);
532 let mut buf = Doc::default();
533
534 if let Some(t) = &info.descr {
535 buf.token(Token::BlockStart(Block::Block));
536 buf.doc(t);
537 buf.token(Token::BlockEnd(Block::Block));
538 }
539
540 buf.token(Token::BlockStart(Block::Block));
541 if let Some(usage) = &info.usage {
542 buf.doc(usage);
543 } else {
544 buf.write_str("Usage", Style::Emphasis);
545 buf.write_str(": ", Style::Text);
546 buf.token(Token::BlockStart(Block::Mono));
547 buf.write_path(path);
548 buf.write_meta(parser_meta, true);
549 buf.token(Token::BlockEnd(Block::Mono));
550 }
551 buf.token(Token::BlockEnd(Block::Block));
552
553 if let Some(t) = &info.header {
554 buf.token(Token::BlockStart(Block::Block));
555 buf.doc(t);
556 buf.token(Token::BlockEnd(Block::Block));
557 }
558
559 let mut items = HelpItems::default();
560 items.append_meta(parser_meta);
561 items.append_meta(help_meta);
562
563 buf.write_help_item_groups(items, include_env);
564
565 if let Some(footer) = &info.footer {
566 buf.token(Token::BlockStart(Block::Block));
567 buf.doc(footer);
568 buf.token(Token::BlockEnd(Block::Block));
569 }
570 buf
571}
572
573#[derive(Default)]
574struct Dedup {
575 items: BTreeSet<String>,
576 keep: bool,
577}
578
579impl Dedup {
580 fn check(&mut self, item: &HelpItem) -> bool {
581 match item {
582 HelpItem::DecorSuffix { .. } => std::mem::take(&mut self.keep),
583 HelpItem::GroupStart { .. }
584 | HelpItem::GroupEnd { .. }
585 | HelpItem::AnywhereStart { .. }
586 | HelpItem::AnywhereStop { .. } => {
587 self.keep = true;
588 true
589 }
590 HelpItem::Any { metavar, help, .. } => {
591 self.keep = self.items.insert(format!("{:?} {:?}", metavar, help));
592 self.keep
593 }
594 HelpItem::Positional { metavar, help } => {
595 self.keep = self.items.insert(format!("{:?} {:?}", metavar.0, help));
596 self.keep
597 }
598 HelpItem::Command { name, help, .. } => {
599 self.keep = self.items.insert(format!("{:?} {:?}", name, help));
600 self.keep
601 }
602 HelpItem::Flag { name, help, .. } => {
603 self.keep = self.items.insert(format!("{:?} {:?}", name, help));
604 self.keep
605 }
606 HelpItem::Argument {
607 name,
608 metavar,
609 help,
610 ..
611 } => {
612 self.keep = self
613 .items
614 .insert(format!("{:?} {} {:?}", name, metavar.0, help));
615 self.keep
616 }
617 }
618 }
619}
620
621impl Doc {
622 #[inline(never)]
623 pub(crate) fn write_help_item_groups(&mut self, mut items: HelpItems, include_env: bool) {
624 while let Some(range) = items.find_group() {
625 let mut dd = Dedup::default();
626 for item in items.items.drain(range) {
627 if dd.check(&item) {
628 write_help_item(self, &item, include_env);
629 }
630 }
631 }
632
633 for (ty, name) in [
634 (HiTy::Positional, "Available positional items:"),
635 (HiTy::Flag, "Available options:"),
636 (HiTy::Command, "Available commands:"),
637 ] {
638 self.write_help_items(&items, ty, name, include_env);
639 }
640 }
641
642 #[inline(never)]
643 fn write_help_items(&mut self, items: &HelpItems, ty: HiTy, name: &str, include_env: bool) {
644 let mut xs = items.items_of_ty(ty).peekable();
645 if xs.peek().is_some() {
646 self.token(Token::BlockStart(Block::Block));
647 self.token(Token::BlockStart(Block::Section2));
648 self.write_str(name, Style::Emphasis);
649 self.token(Token::BlockEnd(Block::Section2));
650 self.token(Token::BlockStart(Block::DefinitionList));
651 let mut dd = Dedup::default();
652 for item in xs {
653 if dd.check(item) {
654 write_help_item(self, item, include_env);
655 }
656 }
657 self.token(Token::BlockEnd(Block::DefinitionList));
658 self.token(Token::BlockEnd(Block::Block));
659 }
660 }
661
662 pub(crate) fn write_path(&mut self, path: &[String]) {
663 for item in path {
664 self.write_str(item, Style::Literal);
665 self.write_char(' ', Style::Text);
666 }
667 }
668}