1#[cfg(test)]
7#[path = "./help_test.rs"]
8mod help_test;
9
10use crate::types::{
11 Argument, ArgumentHelp, ArgumentValueType, CliSpec, Command, PositionalArgument,
12};
13
14pub(crate) fn version(spec: &CliSpec) -> String {
16 let mut buffer = String::new();
17
18 match spec.meta_info {
19 Some(ref meta_info) => {
20 if let Some(ref project) = meta_info.project {
21 buffer.push_str(&project);
22 }
23
24 if let Some(ref version) = meta_info.version {
25 if !buffer.is_empty() {
26 buffer.push_str(" ");
27 }
28 buffer.push_str(&version);
29 }
30 }
31 None => (),
32 }
33
34 buffer
35}
36
37pub(crate) fn help(spec: &CliSpec) -> String {
39 let mut buffer = version(spec);
40
41 match spec.meta_info {
42 Some(ref meta_info) => {
43 if let Some(ref author) = meta_info.author {
44 if !buffer.is_empty() {
45 buffer.push_str("\n");
46 }
47 buffer.push_str(&author);
48 }
49
50 if let Some(ref description) = meta_info.description {
51 if !buffer.is_empty() {
52 buffer.push_str("\n");
53 }
54 buffer.push_str(&description);
55 }
56 }
57 None => (),
58 }
59
60 if !buffer.is_empty() {
61 buffer.push_str("\n\n");
62 }
63
64 if append_usage_line(spec, &mut buffer) {
65 buffer.push_str("\n\n");
66 }
67 if append_args_line(spec, &mut buffer) {
68 buffer.push_str("\n\n");
69 }
70 if append_options_block(spec, &mut buffer) {
71 buffer.push_str("\n\n");
72 }
73
74 match spec.meta_info {
75 Some(ref meta_info) => match meta_info.help_post_text {
76 Some(ref text) => buffer.push_str(text),
77 None => (),
78 },
79 None => (),
80 }
81
82 buffer = buffer.trim().to_string();
83 buffer.push_str("\n");
84
85 buffer
86}
87
88fn append_usage_line(spec: &CliSpec, buffer: &mut String) -> bool {
89 if spec.command.is_empty() && spec.arguments.is_empty() && spec.positional_argument.is_none() {
90 return false;
91 }
92
93 buffer.push_str("USAGE:\n ");
94 let mut sub_buffer = String::new();
95 let mut multiple = false;
96 let mut added_content = false;
97 for command in &spec.command {
98 if !sub_buffer.is_empty() {
99 sub_buffer.push_str(" | ");
100 multiple = true;
101 added_content = true;
102 }
103
104 match command {
105 Command::Command(value) => sub_buffer.push_str(value),
106 Command::SubCommand(values) => sub_buffer.push_str(values.join(" ").as_str()),
107 }
108 }
109 if multiple {
110 buffer.push_str("[");
111 }
112 buffer.push_str(&sub_buffer);
113 if multiple {
114 buffer.push_str("]");
115 }
116
117 if !spec.arguments.is_empty() {
118 if added_content {
119 buffer.push_str(" ");
120 }
121 added_content = true;
122 buffer.push_str("[OPTIONS]");
123 }
124
125 if let Some(ref positional_argument_spec) = spec.positional_argument {
126 let name = get_positional_argument_value_name(positional_argument_spec);
127
128 if added_content {
129 buffer.push_str(" ");
130 }
131 buffer.push_str("[--] [<");
132 buffer.push_str(&name);
133 buffer.push_str(">...]");
134 }
135
136 true
137}
138
139fn append_args_line(spec: &CliSpec, buffer: &mut String) -> bool {
140 if let Some(ref positional_argument_spec) = spec.positional_argument {
141 let name = get_positional_argument_value_name(positional_argument_spec);
142
143 buffer.push_str("ARGS:\n <");
144 buffer.push_str(&name);
145 buffer.push_str(">");
146
147 if let Some(ref help) = positional_argument_spec.help {
148 buffer.push_str(" ");
149
150 match help {
151 ArgumentHelp::Text(text) => buffer.push_str(text),
152 ArgumentHelp::TextAndParam(text, _) => buffer.push_str(text),
153 }
154 }
155
156 true
157 } else {
158 false
159 }
160}
161
162fn append_options_block(spec: &CliSpec, buffer: &mut String) -> bool {
163 if !spec.arguments.is_empty() {
164 let mut names = vec![];
165 let mut max_width = 0;
166
167 for argument in &spec.arguments {
168 let mut sub_buffer = String::new();
169 sub_buffer.push_str(" ");
170 let mut added = false;
171 for key in &argument.key {
172 if added {
173 sub_buffer.push_str(", ");
174 }
175 added = true;
176 sub_buffer.push_str(&key);
177 }
178
179 let value_name = get_argument_value_name(argument);
180 if let Some(name) = value_name {
181 sub_buffer.push_str(" <");
182 sub_buffer.push_str(&name);
183 sub_buffer.push_str(">");
184 }
185
186 let name_len = sub_buffer.len();
187 names.push(sub_buffer);
188 if max_width < name_len {
189 max_width = name_len;
190 }
191 }
192
193 buffer.push_str("OPTIONS:\n");
194
195 let mut index = 0;
196 let help_offset = max_width + 4;
197 for argument in &spec.arguments {
198 if index > 0 {
199 buffer.push_str("\n");
200 }
201
202 let help_text = match argument.help {
203 Some(ref help) => {
204 let mut text = match help {
205 ArgumentHelp::Text(ref text) => text.to_string(),
206 ArgumentHelp::TextAndParam(ref text, _) => text.to_string(),
207 };
208 if !text.is_empty() {
209 text.push_str(" ");
210 }
211
212 text
213 }
214 None => "".to_string(),
215 };
216
217 let default_text = match argument.value_type {
218 ArgumentValueType::None => "".to_string(),
219 _ => match argument.default_value {
220 Some(ref value) => format!("[default: {}]", value),
221 None => "".to_string(),
222 },
223 };
224
225 let line = format!(
226 "{:<help_offset$}{}{}",
227 &names[index],
228 &help_text,
229 &default_text,
230 help_offset = help_offset
231 );
232 buffer.push_str(&line.trim_end());
233 index = index + 1;
234 }
235
236 true
237 } else {
238 false
239 }
240}
241
242fn get_positional_argument_value_name(positional_argument_spec: &PositionalArgument) -> String {
243 match positional_argument_spec.help {
244 Some(ref help) => match help {
245 ArgumentHelp::TextAndParam(_, value) => value.to_string(),
246 ArgumentHelp::Text(_) => positional_argument_spec.name.clone(),
247 },
248 None => positional_argument_spec.name.clone(),
249 }
250}
251
252fn get_argument_value_name(argument_spec: &Argument) -> Option<String> {
253 if argument_spec.value_type == ArgumentValueType::None {
254 None
255 } else {
256 let name = match argument_spec.help {
257 Some(ref help) => match help {
258 ArgumentHelp::TextAndParam(_, value) => value.to_string(),
259 ArgumentHelp::Text(_) => argument_spec.name.clone(),
260 },
261 None => argument_spec.name.clone(),
262 };
263
264 Some(name)
265 }
266}