1use clap::builder::OsStr;
4
5use crate::doc_registry::CommandDoc;
6
7#[derive(Debug, Clone)]
9pub struct HelpPage {
10 pub app_name: String,
12
13 pub version: Option<String>,
15
16 pub path: String,
18
19 pub summary: Option<String>,
21
22 pub description: Option<String>,
24
25 pub usage: String,
27
28 pub positionals: Vec<HelpArg>,
30
31 pub options: Vec<HelpOption>,
33
34 pub subcommands: Vec<HelpSubcommand>,
36
37 pub examples: Vec<String>,
39
40 pub notes: Vec<String>,
42}
43
44#[derive(Debug, Clone)]
46pub struct HelpOption {
47 pub short: Option<char>,
48 pub long: Option<String>,
49 pub value: Option<String>,
50 pub description: String,
51 pub default: String,
52}
53
54#[derive(Debug, Clone)]
56pub struct HelpArg {
57 pub name: String,
58 pub description: Option<String>,
59 pub required: bool,
60 pub multiple: bool,
61}
62
63#[derive(Debug, Clone)]
65pub struct HelpSubcommand {
66 pub name: String,
67 pub summary: Option<String>,
68}
69
70impl HelpPage {
71 pub fn from_clap(
73 app_name: &str,
74 version: Option<&str>,
75 path: &str,
76 cmd: &clap::Command,
77 ) -> Self {
78 let positionals = cmd
79 .get_positionals()
80 .map(|arg| HelpArg {
81 name: arg.get_id().to_string(),
82 description: arg.get_help().map(|s| s.to_string()),
83 required: arg.is_required_set(),
84 multiple: arg
85 .get_num_args()
86 .map(|n| n.min_values() != n.max_values() || 1 < n.min_values())
87 .unwrap_or_default(),
88 })
89 .collect();
90
91 let options = cmd
92 .get_arguments()
93 .filter(|a| !a.is_positional())
94 .map(|arg| HelpOption {
95 short: arg.get_short(),
96 long: arg.get_long().map(str::to_string),
97 value: if arg.get_action().takes_values() {
98 arg.get_value_names()
99 .and_then(|v| v.first())
100 .map(|v| v.to_string())
101 } else {
102 None
103 },
104 description: arg.get_help().unwrap_or_default().to_string(),
105 default: arg
106 .get_default_values()
107 .join(&OsStr::from(", "))
108 .to_str()
109 .unwrap_or_default()
110 .to_string(),
111 })
112 .collect();
113
114 let subcommands = cmd
115 .get_subcommands()
116 .map(|sc| HelpSubcommand {
117 name: sc.get_name().to_string(),
118 summary: sc.get_about().map(|s| s.to_string()),
119 })
120 .collect();
121
122 Self {
123 app_name: app_name.to_string(),
124 version: version.map(|s| s.to_string()),
125 path: path.to_string(),
126 summary: cmd.get_about().map(|s| s.to_string()),
127 description: cmd.get_long_about().map(|s| s.to_string()),
128 usage: cmd.clone().render_usage().to_string(),
129 positionals,
130 options,
131 subcommands,
132 examples: Vec::new(),
133 notes: Vec::new(),
134 }
135 }
136
137 pub fn with_docs(mut self, doc: Option<&CommandDoc>) -> Self {
139 if let Some(doc) = doc {
140 self.description = doc.description.clone().or(self.description);
141 self.examples = doc.examples.clone();
142 self.notes = doc.notes.clone();
143 }
144 self
145 }
146}