1#![cfg_attr(not(feature = "usage"), allow(unused_imports))]
2#![cfg_attr(not(feature = "usage"), allow(unused_variables))]
3#![cfg_attr(not(feature = "usage"), allow(clippy::manual_map))]
4#![cfg_attr(not(feature = "usage"), allow(dead_code))]
5
6use crate::builder::ArgAction;
8use crate::builder::StyledStr;
9use crate::builder::Styles;
10use crate::builder::{ArgPredicate, Command};
11use crate::parser::ArgMatcher;
12use crate::util::ChildGraph;
13use crate::util::FlatSet;
14use crate::util::Id;
15
16static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND";
17const USAGE_SEP: &str = "\n ";
18
19pub(crate) struct Usage<'cmd> {
20 cmd: &'cmd Command,
21 styles: &'cmd Styles,
22 required: Option<&'cmd ChildGraph<Id>>,
23}
24
25impl<'cmd> Usage<'cmd> {
26 pub(crate) fn new(cmd: &'cmd Command) -> Self {
27 Usage {
28 cmd,
29 styles: cmd.get_styles(),
30 required: None,
31 }
32 }
33
34 pub(crate) fn required(mut self, required: &'cmd ChildGraph<Id>) -> Self {
35 self.required = Some(required);
36 self
37 }
38
39 pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr> {
42 debug!("Usage::create_usage_with_title");
43 use std::fmt::Write as _;
44 let mut styled = StyledStr::new();
45 let _ = write!(
46 styled,
47 "{}Usage:{} ",
48 self.styles.get_usage().render(),
49 self.styles.get_usage().render_reset()
50 );
51 if self.write_usage_no_title(&mut styled, used) {
52 styled.trim_end();
53 } else {
54 return None;
55 }
56 debug!("Usage::create_usage_with_title: usage={styled}");
57 Some(styled)
58 }
59
60 pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr> {
62 debug!("Usage::create_usage_no_title");
63
64 let mut styled = StyledStr::new();
65 if self.write_usage_no_title(&mut styled, used) {
66 styled.trim_end();
67 debug!("Usage::create_usage_no_title: usage={styled}");
68 Some(styled)
69 } else {
70 None
71 }
72 }
73
74 fn write_usage_no_title(&self, styled: &mut StyledStr, used: &[Id]) -> bool {
76 debug!("Usage::create_usage_no_title");
77 if let Some(u) = self.cmd.get_override_usage() {
78 styled.push_styled(u);
79 true
80 } else {
81 #[cfg(feature = "usage")]
82 {
83 if used.is_empty() {
84 self.write_help_usage(styled);
85 } else {
86 self.write_smart_usage(styled, used);
87 }
88 true
89 }
90
91 #[cfg(not(feature = "usage"))]
92 {
93 false
94 }
95 }
96 }
97}
98
99#[cfg(feature = "usage")]
100impl Usage<'_> {
101 fn write_help_usage(&self, styled: &mut StyledStr) {
103 debug!("Usage::write_help_usage");
104 use std::fmt::Write;
105
106 if self.cmd.has_visible_subcommands() && self.cmd.is_flatten_help_set() {
107 if !self.cmd.is_subcommand_required_set()
108 || self.cmd.is_args_conflicts_with_subcommands_set()
109 {
110 self.write_arg_usage(styled, &[], true);
111 styled.trim_end();
112 let _ = write!(styled, "{USAGE_SEP}");
113 }
114 let mut cmd = self.cmd.clone();
115 cmd.build();
116 for (i, sub) in cmd
117 .get_subcommands()
118 .filter(|c| !c.is_hide_set())
119 .enumerate()
120 {
121 if i != 0 {
122 styled.trim_end();
123 let _ = write!(styled, "{USAGE_SEP}");
124 }
125 Usage::new(sub).write_usage_no_title(styled, &[]);
126 }
127 } else {
128 self.write_arg_usage(styled, &[], true);
129 self.write_subcommand_usage(styled);
130 }
131 }
132
133 fn write_smart_usage(&self, styled: &mut StyledStr, used: &[Id]) {
136 debug!("Usage::create_smart_usage");
137 use std::fmt::Write;
138 let placeholder = &self.styles.get_placeholder();
139
140 self.write_arg_usage(styled, used, true);
141
142 if self.cmd.is_subcommand_required_set() {
143 let value_name = self
144 .cmd
145 .get_subcommand_value_name()
146 .unwrap_or(DEFAULT_SUB_VALUE_NAME);
147 let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
148 }
149 }
150
151 fn write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool) {
152 debug!("Usage::write_arg_usage; incl_reqs={incl_reqs:?}");
153 use std::fmt::Write as _;
154 let literal = &self.styles.get_literal();
155 let placeholder = &self.styles.get_placeholder();
156
157 let bin_name = self.cmd.get_usage_name_fallback();
158 if !bin_name.is_empty() {
159 let _ = write!(styled, "{literal}{bin_name}{literal:#} ",);
161 }
162
163 if used.is_empty() && self.needs_options_tag() {
164 let _ = write!(styled, "{placeholder}[OPTIONS]{placeholder:#} ",);
165 }
166
167 self.write_args(styled, used, !incl_reqs);
168 }
169
170 fn write_subcommand_usage(&self, styled: &mut StyledStr) {
171 debug!("Usage::write_subcommand_usage");
172 use std::fmt::Write as _;
173
174 if self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set() {
176 let literal = &self.styles.get_literal();
177 let placeholder = &self.styles.get_placeholder();
178 let value_name = self
179 .cmd
180 .get_subcommand_value_name()
181 .unwrap_or(DEFAULT_SUB_VALUE_NAME);
182 if self.cmd.is_subcommand_negates_reqs_set()
183 || self.cmd.is_args_conflicts_with_subcommands_set()
184 {
185 styled.trim_end();
186 let _ = write!(styled, "{USAGE_SEP}");
187 if self.cmd.is_args_conflicts_with_subcommands_set() {
188 let bin_name = self.cmd.get_usage_name_fallback();
189 let _ = write!(styled, "{literal}{bin_name}{literal:#} ",);
191 } else {
192 self.write_arg_usage(styled, &[], false);
193 }
194 let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
195 } else if self.cmd.is_subcommand_required_set() {
196 let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
197 } else {
198 let _ = write!(styled, "{placeholder}[{value_name}]{placeholder:#}",);
199 }
200 }
201 }
202
203 fn needs_options_tag(&self) -> bool {
205 debug!("Usage::needs_options_tag");
206 'outer: for f in self.cmd.get_non_positionals() {
207 debug!("Usage::needs_options_tag:iter: f={}", f.get_id());
208
209 if f.get_long() == Some("help") || f.get_long() == Some("version") {
211 debug!("Usage::needs_options_tag:iter Option is built-in");
212 continue;
213 }
214 match f.get_action() {
215 ArgAction::Set
216 | ArgAction::Append
217 | ArgAction::SetTrue
218 | ArgAction::SetFalse
219 | ArgAction::Count => {}
220 ArgAction::Help
221 | ArgAction::HelpShort
222 | ArgAction::HelpLong
223 | ArgAction::Version => {
224 debug!("Usage::needs_options_tag:iter Option is built-in");
225 continue;
226 }
227 }
228
229 if f.is_hide_set() {
230 debug!("Usage::needs_options_tag:iter Option is hidden");
231 continue;
232 }
233 if f.is_required_set() {
234 debug!("Usage::needs_options_tag:iter Option is required");
235 continue;
236 }
237 for grp_s in self.cmd.groups_for_arg(f.get_id()) {
238 debug!("Usage::needs_options_tag:iter:iter: grp_s={grp_s:?}");
239 if self.cmd.get_groups().any(|g| g.id == grp_s && g.required) {
240 debug!("Usage::needs_options_tag:iter:iter: Group is required");
241 continue 'outer;
242 }
243 }
244
245 debug!("Usage::needs_options_tag:iter: [OPTIONS] required");
246 return true;
247 }
248
249 debug!("Usage::needs_options_tag: [OPTIONS] not required");
250 false
251 }
252
253 pub(crate) fn write_args(&self, styled: &mut StyledStr, incls: &[Id], force_optional: bool) {
255 debug!("Usage::write_args: incls={incls:?}",);
256 use std::fmt::Write as _;
257 let literal = &self.styles.get_literal();
258
259 let required_owned;
260 let required = if let Some(required) = self.required {
261 required
262 } else {
263 required_owned = self.cmd.required_graph();
264 &required_owned
265 };
266
267 let mut unrolled_reqs = Vec::new();
268 for a in required.iter() {
269 let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
270 let required = match val {
271 ArgPredicate::Equals(_) => false,
272 ArgPredicate::IsPresent => true,
273 };
274 required.then(|| req_arg.clone())
275 };
276
277 for aa in self.cmd.unroll_arg_requires(is_relevant, a) {
278 unrolled_reqs.push(aa);
281 }
282 unrolled_reqs.push(a.clone());
285 }
286 debug!("Usage::get_args: unrolled_reqs={unrolled_reqs:?}");
287
288 let mut required_groups_members = FlatSet::new();
289 let mut required_groups = FlatSet::new();
290 for req in unrolled_reqs.iter().chain(incls.iter()) {
291 if self.cmd.find_group(req).is_some() {
292 let group_members = self.cmd.unroll_args_in_group(req);
293 let elem = self.cmd.format_group(req);
294 required_groups.insert(elem);
295 required_groups_members.extend(group_members);
296 } else {
297 debug_assert!(self.cmd.find(req).is_some());
298 }
299 }
300
301 let mut required_opts = FlatSet::new();
302 let mut required_positionals = Vec::new();
303 for req in unrolled_reqs.iter().chain(incls.iter()) {
304 if let Some(arg) = self.cmd.find(req) {
305 if required_groups_members.contains(arg.get_id()) {
306 continue;
307 }
308
309 let stylized = arg.stylized(self.styles, Some(!force_optional));
310 if let Some(index) = arg.get_index() {
311 let new_len = index + 1;
312 if required_positionals.len() < new_len {
313 required_positionals.resize(new_len, None);
314 }
315 required_positionals[index] = Some(stylized);
316 } else {
317 required_opts.insert(stylized);
318 }
319 } else {
320 debug_assert!(self.cmd.find_group(req).is_some());
321 }
322 }
323
324 for pos in self.cmd.get_positionals() {
325 if pos.is_hide_set() {
326 continue;
327 }
328 if required_groups_members.contains(pos.get_id()) {
329 continue;
330 }
331
332 let index = pos.get_index().unwrap();
333 let new_len = index + 1;
334 if required_positionals.len() < new_len {
335 required_positionals.resize(new_len, None);
336 }
337 if required_positionals[index].is_some() {
338 if pos.is_last_set() {
339 let styled = required_positionals[index].take().unwrap();
340 let mut new = StyledStr::new();
341 let _ = write!(new, "{literal}--{literal:#} ");
342 new.push_styled(&styled);
343 required_positionals[index] = Some(new);
344 }
345 } else {
346 let mut styled;
347 if pos.is_last_set() {
348 styled = StyledStr::new();
349 let _ = write!(styled, "{literal}[--{literal:#} ");
350 styled.push_styled(&pos.stylized(self.styles, Some(true)));
351 let _ = write!(styled, "{literal}]{literal:#}");
352 } else {
353 styled = pos.stylized(self.styles, Some(false));
354 }
355 required_positionals[index] = Some(styled);
356 }
357 if pos.is_last_set() && force_optional {
358 required_positionals[index] = None;
359 }
360 }
361
362 if !force_optional {
363 for arg in required_opts {
364 styled.push_styled(&arg);
365 styled.push_str(" ");
366 }
367 for arg in required_groups {
368 styled.push_styled(&arg);
369 styled.push_str(" ");
370 }
371 }
372 for arg in required_positionals.into_iter().flatten() {
373 styled.push_styled(&arg);
374 styled.push_str(" ");
375 }
376 }
377
378 pub(crate) fn get_required_usage_from(
379 &self,
380 incls: &[Id],
381 matcher: Option<&ArgMatcher>,
382 incl_last: bool,
383 ) -> Vec<StyledStr> {
384 debug!(
385 "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}",
386 incls,
387 matcher.is_some(),
388 incl_last
389 );
390
391 let required_owned;
392 let required = if let Some(required) = self.required {
393 required
394 } else {
395 required_owned = self.cmd.required_graph();
396 &required_owned
397 };
398
399 let mut unrolled_reqs = Vec::new();
400 for a in required.iter() {
401 let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
402 let required = match val {
403 ArgPredicate::Equals(_) => {
404 if let Some(matcher) = matcher {
405 matcher.check_explicit(a, val)
406 } else {
407 false
408 }
409 }
410 ArgPredicate::IsPresent => true,
411 };
412 required.then(|| req_arg.clone())
413 };
414
415 for aa in self.cmd.unroll_arg_requires(is_relevant, a) {
416 unrolled_reqs.push(aa);
419 }
420 unrolled_reqs.push(a.clone());
423 }
424 debug!("Usage::get_required_usage_from: unrolled_reqs={unrolled_reqs:?}");
425
426 let mut required_groups_members = FlatSet::new();
427 let mut required_groups = FlatSet::new();
428 for req in unrolled_reqs.iter().chain(incls.iter()) {
429 if self.cmd.find_group(req).is_some() {
430 let group_members = self.cmd.unroll_args_in_group(req);
431 let is_present = matcher
432 .map(|m| {
433 group_members
434 .iter()
435 .any(|arg| m.check_explicit(arg, &ArgPredicate::IsPresent))
436 })
437 .unwrap_or(false);
438 debug!("Usage::get_required_usage_from:iter:{req:?} group is_present={is_present}");
439 if is_present {
440 continue;
441 }
442
443 let elem = self.cmd.format_group(req);
444 required_groups.insert(elem);
445 required_groups_members.extend(group_members);
446 } else {
447 debug_assert!(self.cmd.find(req).is_some(), "`{req}` must exist");
448 }
449 }
450
451 let mut required_opts = FlatSet::new();
452 let mut required_positionals = Vec::new();
453 for req in unrolled_reqs.iter().chain(incls.iter()) {
454 if let Some(arg) = self.cmd.find(req) {
455 if required_groups_members.contains(arg.get_id()) {
456 continue;
457 }
458
459 let is_present = matcher
460 .map(|m| m.check_explicit(req, &ArgPredicate::IsPresent))
461 .unwrap_or(false);
462 debug!("Usage::get_required_usage_from:iter:{req:?} arg is_present={is_present}");
463 if is_present {
464 continue;
465 }
466
467 let stylized = arg.stylized(self.styles, Some(true));
468 if let Some(index) = arg.get_index() {
469 if !arg.is_last_set() || incl_last {
470 let new_len = index + 1;
471 if required_positionals.len() < new_len {
472 required_positionals.resize(new_len, None);
473 }
474 required_positionals[index] = Some(stylized);
475 }
476 } else {
477 required_opts.insert(stylized);
478 }
479 } else {
480 debug_assert!(self.cmd.find_group(req).is_some());
481 }
482 }
483
484 let mut ret_val = Vec::new();
485 ret_val.extend(required_opts);
486 ret_val.extend(required_groups);
487 for pos in required_positionals.into_iter().flatten() {
488 ret_val.push(pos);
489 }
490
491 debug!("Usage::get_required_usage_from: ret_val={ret_val:?}");
492 ret_val
493 }
494}