]>
Commit | Line | Data |
---|---|---|
8bb4bdeb | 1 | // Std |
abe05a73 | 2 | use std::borrow::Cow; |
8bb4bdeb XL |
3 | use std::cmp; |
4 | use std::collections::BTreeMap; | |
5 | use std::fmt::Display; | |
6 | use std::io::{self, Cursor, Read, Write}; | |
7 | use std::usize; | |
8 | ||
9 | // Internal | |
8bb4bdeb | 10 | use app::parser::Parser; |
8faf50e0 XL |
11 | use app::usage; |
12 | use app::{App, AppSettings}; | |
8bb4bdeb XL |
13 | use args::{AnyArg, ArgSettings, DispOrder}; |
14 | use errors::{Error, Result as ClapResult}; | |
abe05a73 | 15 | use fmt::{Colorizer, ColorizerOption, Format}; |
abe05a73 XL |
16 | use map::VecMap; |
17 | use INTERNAL_ERROR_MSG; | |
8bb4bdeb XL |
18 | |
19 | // Third Party | |
8bb4bdeb XL |
20 | #[cfg(feature = "wrap_help")] |
21 | use term_size; | |
041b39d2 | 22 | use textwrap; |
8faf50e0 | 23 | use unicode_width::UnicodeWidthStr; |
8bb4bdeb XL |
24 | |
25 | #[cfg(not(feature = "wrap_help"))] | |
26 | mod term_size { | |
8faf50e0 XL |
27 | pub fn dimensions() -> Option<(usize, usize)> { |
28 | None | |
29 | } | |
8bb4bdeb XL |
30 | } |
31 | ||
8faf50e0 XL |
32 | fn str_width(s: &str) -> usize { |
33 | UnicodeWidthStr::width(s) | |
34 | } | |
8bb4bdeb XL |
35 | |
36 | const TAB: &'static str = " "; | |
37 | ||
38 | // These are just convenient traits to make the code easier to read. | |
39 | trait ArgWithDisplay<'b, 'c>: AnyArg<'b, 'c> + Display {} | |
3dfed10e | 40 | impl<'b, 'c, T> ArgWithDisplay<'b, 'c> for T where T: AnyArg<'b, 'c> + Display {} |
8bb4bdeb XL |
41 | |
42 | trait ArgWithOrder<'b, 'c>: ArgWithDisplay<'b, 'c> + DispOrder { | |
43 | fn as_base(&self) -> &ArgWithDisplay<'b, 'c>; | |
44 | } | |
45 | impl<'b, 'c, T> ArgWithOrder<'b, 'c> for T | |
abe05a73 XL |
46 | where |
47 | T: ArgWithDisplay<'b, 'c> + DispOrder, | |
8bb4bdeb | 48 | { |
8faf50e0 XL |
49 | fn as_base(&self) -> &ArgWithDisplay<'b, 'c> { |
50 | self | |
51 | } | |
8bb4bdeb XL |
52 | } |
53 | ||
8faf50e0 XL |
54 | fn as_arg_trait<'a, 'b, T: ArgWithOrder<'a, 'b>>(x: &T) -> &ArgWithOrder<'a, 'b> { |
55 | x | |
56 | } | |
8bb4bdeb XL |
57 | |
58 | impl<'b, 'c> DispOrder for App<'b, 'c> { | |
8faf50e0 XL |
59 | fn disp_ord(&self) -> usize { |
60 | 999 | |
61 | } | |
8bb4bdeb XL |
62 | } |
63 | ||
64 | macro_rules! color { | |
65 | ($_self:ident, $s:expr, $c:ident) => { | |
66 | if $_self.color { | |
67 | write!($_self.writer, "{}", $_self.cizer.$c($s)) | |
68 | } else { | |
69 | write!($_self.writer, "{}", $s) | |
70 | } | |
71 | }; | |
72 | ($_self:ident, $fmt_s:expr, $v:expr, $c:ident) => { | |
73 | if $_self.color { | |
74 | write!($_self.writer, "{}", $_self.cizer.$c(format!($fmt_s, $v))) | |
75 | } else { | |
76 | write!($_self.writer, $fmt_s, $v) | |
77 | } | |
78 | }; | |
79 | } | |
80 | ||
81 | /// `clap` Help Writer. | |
82 | /// | |
83 | /// Wraps a writer stream providing different methods to generate help for `clap` objects. | |
84 | pub struct Help<'a> { | |
85 | writer: &'a mut Write, | |
86 | next_line_help: bool, | |
87 | hide_pv: bool, | |
88 | term_w: usize, | |
89 | color: bool, | |
90 | cizer: Colorizer, | |
91 | longest: usize, | |
92 | force_next_line: bool, | |
041b39d2 | 93 | use_long: bool, |
8bb4bdeb XL |
94 | } |
95 | ||
96 | // Public Functions | |
97 | impl<'a> Help<'a> { | |
98 | /// Create a new `Help` instance. | |
041b39d2 | 99 | #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] |
abe05a73 XL |
100 | pub fn new( |
101 | w: &'a mut Write, | |
102 | next_line_help: bool, | |
103 | hide_pv: bool, | |
104 | color: bool, | |
105 | cizer: Colorizer, | |
106 | term_w: Option<usize>, | |
107 | max_w: Option<usize>, | |
108 | use_long: bool, | |
109 | ) -> Self { | |
041b39d2 | 110 | debugln!("Help::new;"); |
8bb4bdeb XL |
111 | Help { |
112 | writer: w, | |
113 | next_line_help: next_line_help, | |
114 | hide_pv: hide_pv, | |
115 | term_w: match term_w { | |
3dfed10e XL |
116 | Some(width) => { |
117 | if width == 0 { | |
118 | usize::MAX | |
119 | } else { | |
120 | width | |
121 | } | |
122 | } | |
abe05a73 XL |
123 | None => cmp::min( |
124 | term_size::dimensions().map_or(120, |(w, _)| w), | |
125 | match max_w { | |
126 | None | Some(0) => usize::MAX, | |
127 | Some(mw) => mw, | |
128 | }, | |
129 | ), | |
8bb4bdeb XL |
130 | }, |
131 | color: color, | |
132 | cizer: cizer, | |
133 | longest: 0, | |
134 | force_next_line: false, | |
041b39d2 | 135 | use_long: use_long, |
8bb4bdeb XL |
136 | } |
137 | } | |
138 | ||
139 | /// Reads help settings from an App | |
140 | /// and write its help to the wrapped stream. | |
041b39d2 XL |
141 | pub fn write_app_help(w: &'a mut Write, app: &App, use_long: bool) -> ClapResult<()> { |
142 | debugln!("Help::write_app_help;"); | |
143 | Self::write_parser_help(w, &app.p, use_long) | |
8bb4bdeb XL |
144 | } |
145 | ||
146 | /// Reads help settings from a Parser | |
147 | /// and write its help to the wrapped stream. | |
041b39d2 XL |
148 | pub fn write_parser_help(w: &'a mut Write, parser: &Parser, use_long: bool) -> ClapResult<()> { |
149 | debugln!("Help::write_parser_help;"); | |
150 | Self::_write_parser_help(w, parser, false, use_long) | |
8bb4bdeb XL |
151 | } |
152 | ||
153 | /// Reads help settings from a Parser | |
154 | /// and write its help to the wrapped stream which will be stderr. This method prevents | |
155 | /// formatting when required. | |
156 | pub fn write_parser_help_to_stderr(w: &'a mut Write, parser: &Parser) -> ClapResult<()> { | |
041b39d2 XL |
157 | debugln!("Help::write_parser_help;"); |
158 | Self::_write_parser_help(w, parser, true, false) | |
8bb4bdeb XL |
159 | } |
160 | ||
161 | #[doc(hidden)] | |
abe05a73 XL |
162 | pub fn _write_parser_help( |
163 | w: &'a mut Write, | |
164 | parser: &Parser, | |
165 | stderr: bool, | |
166 | use_long: bool, | |
167 | ) -> ClapResult<()> { | |
041b39d2 | 168 | debugln!("Help::write_parser_help;"); |
8bb4bdeb XL |
169 | let nlh = parser.is_set(AppSettings::NextLineHelp); |
170 | let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp); | |
171 | let color = parser.is_set(AppSettings::ColoredHelp); | |
041b39d2 | 172 | let cizer = Colorizer::new(ColorizerOption { |
8bb4bdeb XL |
173 | use_stderr: stderr, |
174 | when: parser.color(), | |
041b39d2 | 175 | }); |
abe05a73 XL |
176 | Self::new( |
177 | w, | |
178 | nlh, | |
179 | hide_v, | |
180 | color, | |
181 | cizer, | |
182 | parser.meta.term_w, | |
183 | parser.meta.max_w, | |
184 | use_long, | |
3dfed10e XL |
185 | ) |
186 | .write_help(parser) | |
8bb4bdeb XL |
187 | } |
188 | ||
189 | /// Writes the parser help to the wrapped stream. | |
190 | pub fn write_help(&mut self, parser: &Parser) -> ClapResult<()> { | |
041b39d2 | 191 | debugln!("Help::write_help;"); |
8bb4bdeb | 192 | if let Some(h) = parser.meta.help_str { |
3b2f2976 | 193 | write!(self.writer, "{}", h).map_err(Error::from)?; |
8bb4bdeb | 194 | } else if let Some(tmpl) = parser.meta.template { |
3b2f2976 | 195 | self.write_templated_help(parser, tmpl)?; |
8bb4bdeb | 196 | } else { |
3b2f2976 | 197 | self.write_default_help(parser)?; |
8bb4bdeb XL |
198 | } |
199 | Ok(()) | |
200 | } | |
201 | } | |
202 | ||
203 | // Methods to write AnyArg help. | |
204 | impl<'a> Help<'a> { | |
205 | /// Writes help for each argument in the order they were declared to the wrapped stream. | |
206 | fn write_args_unsorted<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> | |
abe05a73 XL |
207 | where |
208 | I: Iterator<Item = &'d ArgWithOrder<'b, 'c>>, | |
8bb4bdeb | 209 | { |
041b39d2 | 210 | debugln!("Help::write_args_unsorted;"); |
8bb4bdeb XL |
211 | // The shortest an arg can legally be is 2 (i.e. '-x') |
212 | self.longest = 2; | |
213 | let mut arg_v = Vec::with_capacity(10); | |
8faf50e0 XL |
214 | let use_long = self.use_long; |
215 | for arg in args.filter(|arg| should_show_arg(use_long, *arg)) { | |
8bb4bdeb | 216 | if arg.longest_filter() { |
041b39d2 | 217 | self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str())); |
7cac9316 | 218 | } |
041b39d2 | 219 | arg_v.push(arg) |
8bb4bdeb XL |
220 | } |
221 | let mut first = true; | |
222 | for arg in arg_v { | |
223 | if first { | |
224 | first = false; | |
225 | } else { | |
3b2f2976 | 226 | self.writer.write_all(b"\n")?; |
8bb4bdeb | 227 | } |
3b2f2976 | 228 | self.write_arg(arg.as_base())?; |
8bb4bdeb XL |
229 | } |
230 | Ok(()) | |
231 | } | |
232 | ||
233 | /// Sorts arguments by length and display order and write their help to the wrapped stream. | |
234 | fn write_args<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> | |
abe05a73 XL |
235 | where |
236 | I: Iterator<Item = &'d ArgWithOrder<'b, 'c>>, | |
8bb4bdeb | 237 | { |
041b39d2 | 238 | debugln!("Help::write_args;"); |
8bb4bdeb XL |
239 | // The shortest an arg can legally be is 2 (i.e. '-x') |
240 | self.longest = 2; | |
241 | let mut ord_m = VecMap::new(); | |
8faf50e0 | 242 | let use_long = self.use_long; |
8bb4bdeb XL |
243 | // Determine the longest |
244 | for arg in args.filter(|arg| { | |
245 | // If it's NextLineHelp, but we don't care to compute how long because it may be | |
246 | // NextLineHelp on purpose *because* it's so long and would throw off all other | |
247 | // args alignment | |
8faf50e0 | 248 | should_show_arg(use_long, *arg) |
8bb4bdeb XL |
249 | }) { |
250 | if arg.longest_filter() { | |
041b39d2 XL |
251 | debugln!("Help::write_args: Current Longest...{}", self.longest); |
252 | self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str())); | |
253 | debugln!("Help::write_args: New Longest...{}", self.longest); | |
8bb4bdeb XL |
254 | } |
255 | let btm = ord_m.entry(arg.disp_ord()).or_insert(BTreeMap::new()); | |
256 | btm.insert(arg.name(), arg); | |
257 | } | |
258 | let mut first = true; | |
259 | for btm in ord_m.values() { | |
260 | for arg in btm.values() { | |
261 | if first { | |
262 | first = false; | |
263 | } else { | |
3b2f2976 | 264 | self.writer.write_all(b"\n")?; |
8bb4bdeb | 265 | } |
3b2f2976 | 266 | self.write_arg(arg.as_base())?; |
8bb4bdeb XL |
267 | } |
268 | } | |
269 | Ok(()) | |
270 | } | |
271 | ||
272 | /// Writes help for an argument to the wrapped stream. | |
273 | fn write_arg<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { | |
041b39d2 | 274 | debugln!("Help::write_arg;"); |
3b2f2976 XL |
275 | self.short(arg)?; |
276 | self.long(arg)?; | |
277 | let spec_vals = self.val(arg)?; | |
278 | self.help(arg, &*spec_vals)?; | |
8bb4bdeb XL |
279 | Ok(()) |
280 | } | |
281 | ||
282 | /// Writes argument's short command to the wrapped stream. | |
283 | fn short<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { | |
041b39d2 | 284 | debugln!("Help::short;"); |
3b2f2976 | 285 | write!(self.writer, "{}", TAB)?; |
8bb4bdeb XL |
286 | if let Some(s) = arg.short() { |
287 | color!(self, "-{}", s, good) | |
288 | } else if arg.has_switch() { | |
289 | write!(self.writer, "{}", TAB) | |
290 | } else { | |
291 | Ok(()) | |
292 | } | |
293 | } | |
294 | ||
295 | /// Writes argument's long command to the wrapped stream. | |
296 | fn long<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { | |
041b39d2 | 297 | debugln!("Help::long;"); |
8bb4bdeb XL |
298 | if !arg.has_switch() { |
299 | return Ok(()); | |
300 | } | |
301 | if arg.takes_value() { | |
302 | if let Some(l) = arg.long() { | |
303 | if arg.short().is_some() { | |
3b2f2976 | 304 | write!(self.writer, ", ")?; |
8bb4bdeb | 305 | } |
3b2f2976 | 306 | color!(self, "--{}", l, good)? |
8bb4bdeb | 307 | } |
041b39d2 XL |
308 | |
309 | let sep = if arg.is_set(ArgSettings::RequireEquals) { | |
310 | "=" | |
311 | } else { | |
312 | " " | |
313 | }; | |
3b2f2976 | 314 | write!(self.writer, "{}", sep)?; |
8bb4bdeb XL |
315 | } else if let Some(l) = arg.long() { |
316 | if arg.short().is_some() { | |
3b2f2976 | 317 | write!(self.writer, ", ")?; |
8bb4bdeb | 318 | } |
3b2f2976 | 319 | color!(self, "--{}", l, good)?; |
8bb4bdeb XL |
320 | } |
321 | Ok(()) | |
322 | } | |
323 | ||
324 | /// Writes argument's possible values to the wrapped stream. | |
325 | fn val<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> Result<String, io::Error> { | |
041b39d2 | 326 | debugln!("Help::val: arg={}", arg); |
8bb4bdeb | 327 | if arg.takes_value() { |
abe05a73 XL |
328 | let delim = if arg.is_set(ArgSettings::RequireDelimiter) { |
329 | arg.val_delim().expect(INTERNAL_ERROR_MSG) | |
330 | } else { | |
331 | ' ' | |
332 | }; | |
8bb4bdeb XL |
333 | if let Some(vec) = arg.val_names() { |
334 | let mut it = vec.iter().peekable(); | |
335 | while let Some((_, val)) = it.next() { | |
3b2f2976 | 336 | color!(self, "<{}>", val, good)?; |
8bb4bdeb | 337 | if it.peek().is_some() { |
abe05a73 | 338 | write!(self.writer, "{}", delim)?; |
8bb4bdeb XL |
339 | } |
340 | } | |
341 | let num = vec.len(); | |
342 | if arg.is_set(ArgSettings::Multiple) && num == 1 { | |
3b2f2976 | 343 | color!(self, "...", good)?; |
8bb4bdeb XL |
344 | } |
345 | } else if let Some(num) = arg.num_vals() { | |
346 | let mut it = (0..num).peekable(); | |
347 | while let Some(_) = it.next() { | |
3b2f2976 | 348 | color!(self, "<{}>", arg.name(), good)?; |
8bb4bdeb | 349 | if it.peek().is_some() { |
abe05a73 | 350 | write!(self.writer, "{}", delim)?; |
8bb4bdeb XL |
351 | } |
352 | } | |
353 | if arg.is_set(ArgSettings::Multiple) && num == 1 { | |
3b2f2976 | 354 | color!(self, "...", good)?; |
8bb4bdeb XL |
355 | } |
356 | } else if arg.has_switch() { | |
3b2f2976 | 357 | color!(self, "<{}>", arg.name(), good)?; |
8bb4bdeb | 358 | if arg.is_set(ArgSettings::Multiple) { |
3b2f2976 | 359 | color!(self, "...", good)?; |
8bb4bdeb XL |
360 | } |
361 | } else { | |
3b2f2976 | 362 | color!(self, "{}", arg, good)?; |
8bb4bdeb XL |
363 | } |
364 | } | |
365 | ||
366 | let spec_vals = self.spec_vals(arg); | |
367 | let h = arg.help().unwrap_or(""); | |
368 | let h_w = str_width(h) + str_width(&*spec_vals); | |
369 | let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp); | |
370 | let taken = self.longest + 12; | |
3dfed10e XL |
371 | self.force_next_line = !nlh |
372 | && self.term_w >= taken | |
abe05a73 XL |
373 | && (taken as f32 / self.term_w as f32) > 0.40 |
374 | && h_w > (self.term_w - taken); | |
8bb4bdeb | 375 | |
041b39d2 | 376 | debug!("Help::val: Has switch..."); |
8bb4bdeb XL |
377 | if arg.has_switch() { |
378 | sdebugln!("Yes"); | |
041b39d2 XL |
379 | debugln!("Help::val: force_next_line...{:?}", self.force_next_line); |
380 | debugln!("Help::val: nlh...{:?}", nlh); | |
381 | debugln!("Help::val: taken...{}", taken); | |
abe05a73 XL |
382 | debugln!( |
383 | "Help::val: help_width > (width - taken)...{} > ({} - {})", | |
384 | h_w, | |
385 | self.term_w, | |
386 | taken | |
387 | ); | |
041b39d2 XL |
388 | debugln!("Help::val: longest...{}", self.longest); |
389 | debug!("Help::val: next_line..."); | |
8bb4bdeb XL |
390 | if !(nlh || self.force_next_line) { |
391 | sdebugln!("No"); | |
041b39d2 | 392 | let self_len = str_width(arg.to_string().as_str()); |
8bb4bdeb XL |
393 | // subtract ourself |
394 | let mut spcs = self.longest - self_len; | |
395 | // Since we're writing spaces from the tab point we first need to know if we | |
396 | // had a long and short, or just short | |
397 | if arg.long().is_some() { | |
398 | // Only account 4 after the val | |
399 | spcs += 4; | |
400 | } else { | |
401 | // Only account for ', --' + 4 after the val | |
402 | spcs += 8; | |
403 | } | |
404 | ||
405 | write_nspaces!(self.writer, spcs); | |
406 | } else { | |
407 | sdebugln!("Yes"); | |
408 | } | |
409 | } else if !(nlh || self.force_next_line) { | |
410 | sdebugln!("No, and not next_line"); | |
abe05a73 XL |
411 | write_nspaces!( |
412 | self.writer, | |
413 | self.longest + 4 - (str_width(arg.to_string().as_str())) | |
414 | ); | |
8bb4bdeb XL |
415 | } else { |
416 | sdebugln!("No"); | |
417 | } | |
418 | Ok(spec_vals) | |
419 | } | |
420 | ||
421 | fn write_before_after_help(&mut self, h: &str) -> io::Result<()> { | |
041b39d2 XL |
422 | debugln!("Help::write_before_after_help;"); |
423 | let mut help = String::from(h); | |
8bb4bdeb | 424 | // determine if our help fits or needs to wrap |
abe05a73 XL |
425 | debugln!( |
426 | "Help::write_before_after_help: Term width...{}", | |
427 | self.term_w | |
428 | ); | |
8bb4bdeb XL |
429 | let too_long = str_width(h) >= self.term_w; |
430 | ||
041b39d2 | 431 | debug!("Help::write_before_after_help: Too long..."); |
8bb4bdeb XL |
432 | if too_long || h.contains("{n}") { |
433 | sdebugln!("Yes"); | |
041b39d2 | 434 | debugln!("Help::write_before_after_help: help: {}", help); |
abe05a73 XL |
435 | debugln!( |
436 | "Help::write_before_after_help: help width: {}", | |
437 | str_width(&*help) | |
438 | ); | |
8bb4bdeb | 439 | // Determine how many newlines we need to insert |
abe05a73 XL |
440 | debugln!( |
441 | "Help::write_before_after_help: Usable space: {}", | |
442 | self.term_w | |
443 | ); | |
041b39d2 | 444 | help = wrap_help(&help.replace("{n}", "\n"), self.term_w); |
8bb4bdeb XL |
445 | } else { |
446 | sdebugln!("No"); | |
447 | } | |
3b2f2976 | 448 | write!(self.writer, "{}", help)?; |
8bb4bdeb XL |
449 | Ok(()) |
450 | } | |
451 | ||
452 | /// Writes argument's help to the wrapped stream. | |
453 | fn help<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, spec_vals: &str) -> io::Result<()> { | |
041b39d2 | 454 | debugln!("Help::help;"); |
8faf50e0 | 455 | let h = if self.use_long && arg.name() != "" { |
041b39d2 XL |
456 | arg.long_help().unwrap_or_else(|| arg.help().unwrap_or("")) |
457 | } else { | |
458 | arg.help().unwrap_or_else(|| arg.long_help().unwrap_or("")) | |
459 | }; | |
460 | let mut help = String::from(h) + spec_vals; | |
3dfed10e XL |
461 | let nlh = self.next_line_help |
462 | || arg.is_set(ArgSettings::NextLineHelp) | |
463 | || (self.use_long && arg.name() != ""); | |
041b39d2 | 464 | debugln!("Help::help: Next Line...{:?}", nlh); |
8bb4bdeb XL |
465 | |
466 | let spcs = if nlh || self.force_next_line { | |
467 | 12 // "tab" * 3 | |
468 | } else { | |
469 | self.longest + 12 | |
470 | }; | |
471 | ||
472 | let too_long = spcs + str_width(h) + str_width(&*spec_vals) >= self.term_w; | |
473 | ||
474 | // Is help on next line, if so then indent | |
475 | if nlh || self.force_next_line { | |
3b2f2976 | 476 | write!(self.writer, "\n{}{}{}", TAB, TAB, TAB)?; |
8bb4bdeb XL |
477 | } |
478 | ||
041b39d2 | 479 | debug!("Help::help: Too long..."); |
8bb4bdeb XL |
480 | if too_long && spcs <= self.term_w || h.contains("{n}") { |
481 | sdebugln!("Yes"); | |
041b39d2 XL |
482 | debugln!("Help::help: help...{}", help); |
483 | debugln!("Help::help: help width...{}", str_width(&*help)); | |
8bb4bdeb XL |
484 | // Determine how many newlines we need to insert |
485 | let avail_chars = self.term_w - spcs; | |
041b39d2 XL |
486 | debugln!("Help::help: Usable space...{}", avail_chars); |
487 | help = wrap_help(&help.replace("{n}", "\n"), avail_chars); | |
8bb4bdeb XL |
488 | } else { |
489 | sdebugln!("No"); | |
490 | } | |
041b39d2 | 491 | if let Some(part) = help.lines().next() { |
3b2f2976 | 492 | write!(self.writer, "{}", part)?; |
041b39d2 XL |
493 | } |
494 | for part in help.lines().skip(1) { | |
3b2f2976 | 495 | write!(self.writer, "\n")?; |
041b39d2 | 496 | if nlh || self.force_next_line { |
3b2f2976 | 497 | write!(self.writer, "{}{}{}", TAB, TAB, TAB)?; |
041b39d2 XL |
498 | } else if arg.has_switch() { |
499 | write_nspaces!(self.writer, self.longest + 12); | |
500 | } else { | |
501 | write_nspaces!(self.writer, self.longest + 8); | |
8bb4bdeb | 502 | } |
3b2f2976 | 503 | write!(self.writer, "{}", part)?; |
041b39d2 XL |
504 | } |
505 | if !help.contains('\n') && (nlh || self.force_next_line) { | |
3b2f2976 | 506 | write!(self.writer, "\n")?; |
8bb4bdeb XL |
507 | } |
508 | Ok(()) | |
509 | } | |
510 | ||
511 | fn spec_vals(&self, a: &ArgWithDisplay) -> String { | |
041b39d2 | 512 | debugln!("Help::spec_vals: a={}", a); |
8bb4bdeb | 513 | let mut spec_vals = vec![]; |
abe05a73 XL |
514 | if let Some(ref env) = a.env() { |
515 | debugln!( | |
516 | "Help::spec_vals: Found environment variable...[{:?}:{:?}]", | |
517 | env.0, | |
518 | env.1 | |
519 | ); | |
ff7c6d11 | 520 | let env_val = if !a.is_set(ArgSettings::HideEnvValues) { |
8faf50e0 XL |
521 | format!( |
522 | "={}", | |
523 | env.1.map_or(Cow::Borrowed(""), |val| val.to_string_lossy()) | |
524 | ) | |
ff7c6d11 XL |
525 | } else { |
526 | String::new() | |
527 | }; | |
8faf50e0 | 528 | let env_info = format!(" [env: {}{}]", env.0.to_string_lossy(), env_val); |
ff7c6d11 | 529 | spec_vals.push(env_info); |
abe05a73 | 530 | } |
041b39d2 XL |
531 | if !a.is_set(ArgSettings::HideDefaultValue) { |
532 | if let Some(pv) = a.default_val() { | |
533 | debugln!("Help::spec_vals: Found default value...[{:?}]", pv); | |
abe05a73 XL |
534 | spec_vals.push(format!( |
535 | " [default: {}]", | |
536 | if self.color { | |
537 | self.cizer.good(pv.to_string_lossy()) | |
538 | } else { | |
539 | Format::None(pv.to_string_lossy()) | |
540 | } | |
541 | )); | |
041b39d2 | 542 | } |
8bb4bdeb XL |
543 | } |
544 | if let Some(ref aliases) = a.aliases() { | |
041b39d2 | 545 | debugln!("Help::spec_vals: Found aliases...{:?}", aliases); |
abe05a73 XL |
546 | spec_vals.push(format!( |
547 | " [aliases: {}]", | |
548 | if self.color { | |
549 | aliases | |
550 | .iter() | |
551 | .map(|v| format!("{}", self.cizer.good(v))) | |
552 | .collect::<Vec<_>>() | |
553 | .join(", ") | |
554 | } else { | |
555 | aliases.join(", ") | |
556 | } | |
557 | )); | |
8bb4bdeb XL |
558 | } |
559 | if !self.hide_pv && !a.is_set(ArgSettings::HidePossibleValues) { | |
560 | if let Some(pv) = a.possible_vals() { | |
041b39d2 | 561 | debugln!("Help::spec_vals: Found possible vals...{:?}", pv); |
8bb4bdeb | 562 | spec_vals.push(if self.color { |
abe05a73 | 563 | format!( |
0531ce1d | 564 | " [possible values: {}]", |
abe05a73 XL |
565 | pv.iter() |
566 | .map(|v| format!("{}", self.cizer.good(v))) | |
567 | .collect::<Vec<_>>() | |
568 | .join(", ") | |
569 | ) | |
570 | } else { | |
0531ce1d | 571 | format!(" [possible values: {}]", pv.join(", ")) |
abe05a73 | 572 | }); |
8bb4bdeb XL |
573 | } |
574 | } | |
575 | spec_vals.join(" ") | |
576 | } | |
577 | } | |
578 | ||
8faf50e0 XL |
579 | fn should_show_arg(use_long: bool, arg: &ArgWithOrder) -> bool { |
580 | if arg.is_set(ArgSettings::Hidden) { | |
581 | return false; | |
582 | } | |
583 | ||
584 | (!arg.is_set(ArgSettings::HiddenLongHelp) && use_long) | |
585 | || (!arg.is_set(ArgSettings::HiddenShortHelp) && !use_long) | |
586 | || arg.is_set(ArgSettings::NextLineHelp) | |
587 | } | |
8bb4bdeb XL |
588 | |
589 | // Methods to write Parser help. | |
590 | impl<'a> Help<'a> { | |
591 | /// Writes help for all arguments (options, flags, args, subcommands) | |
592 | /// including titles of a Parser Object to the wrapped stream. | |
593 | #[cfg_attr(feature = "lints", allow(useless_let_if_seq))] | |
041b39d2 | 594 | #[cfg_attr(feature = "cargo-clippy", allow(useless_let_if_seq))] |
8bb4bdeb | 595 | pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { |
041b39d2 | 596 | debugln!("Help::write_all_args;"); |
8bb4bdeb | 597 | let flags = parser.has_flags(); |
041b39d2 XL |
598 | let pos = parser |
599 | .positionals() | |
600 | .filter(|arg| !arg.is_set(ArgSettings::Hidden)) | |
3dfed10e XL |
601 | .count() |
602 | > 0; | |
8bb4bdeb | 603 | let opts = parser.has_opts(); |
ea8adc8c | 604 | let subcmds = parser.has_visible_subcommands(); |
8bb4bdeb XL |
605 | |
606 | let unified_help = parser.is_set(AppSettings::UnifiedHelpMessage); | |
607 | ||
608 | let mut first = true; | |
609 | ||
610 | if unified_help && (flags || opts) { | |
041b39d2 XL |
611 | let opts_flags = parser |
612 | .flags() | |
8bb4bdeb XL |
613 | .map(as_arg_trait) |
614 | .chain(parser.opts().map(as_arg_trait)); | |
3b2f2976 XL |
615 | color!(self, "OPTIONS:\n", warning)?; |
616 | self.write_args(opts_flags)?; | |
8bb4bdeb XL |
617 | first = false; |
618 | } else { | |
619 | if flags { | |
3b2f2976 XL |
620 | color!(self, "FLAGS:\n", warning)?; |
621 | self.write_args(parser.flags().map(as_arg_trait))?; | |
8bb4bdeb XL |
622 | first = false; |
623 | } | |
624 | if opts { | |
625 | if !first { | |
3b2f2976 | 626 | self.writer.write_all(b"\n\n")?; |
8bb4bdeb | 627 | } |
3b2f2976 XL |
628 | color!(self, "OPTIONS:\n", warning)?; |
629 | self.write_args(parser.opts().map(as_arg_trait))?; | |
8bb4bdeb XL |
630 | first = false; |
631 | } | |
632 | } | |
633 | ||
634 | if pos { | |
635 | if !first { | |
3b2f2976 | 636 | self.writer.write_all(b"\n\n")?; |
8bb4bdeb | 637 | } |
3b2f2976 XL |
638 | color!(self, "ARGS:\n", warning)?; |
639 | self.write_args_unsorted(parser.positionals().map(as_arg_trait))?; | |
8bb4bdeb XL |
640 | first = false; |
641 | } | |
642 | ||
643 | if subcmds { | |
644 | if !first { | |
3b2f2976 | 645 | self.writer.write_all(b"\n\n")?; |
8bb4bdeb | 646 | } |
3b2f2976 XL |
647 | color!(self, "SUBCOMMANDS:\n", warning)?; |
648 | self.write_subcommands(parser)?; | |
8bb4bdeb XL |
649 | } |
650 | ||
651 | Ok(()) | |
652 | } | |
653 | ||
654 | /// Writes help for subcommands of a Parser Object to the wrapped stream. | |
655 | fn write_subcommands(&mut self, parser: &Parser) -> io::Result<()> { | |
041b39d2 | 656 | debugln!("Help::write_subcommands;"); |
8bb4bdeb XL |
657 | // The shortest an arg can legally be is 2 (i.e. '-x') |
658 | self.longest = 2; | |
659 | let mut ord_m = VecMap::new(); | |
041b39d2 | 660 | for sc in parser |
abe05a73 XL |
661 | .subcommands |
662 | .iter() | |
663 | .filter(|s| !s.p.is_set(AppSettings::Hidden)) | |
664 | { | |
665 | let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); | |
041b39d2 XL |
666 | self.longest = cmp::max(self.longest, str_width(sc.p.meta.name.as_str())); |
667 | //self.longest = cmp::max(self.longest, sc.p.meta.name.len()); | |
8bb4bdeb XL |
668 | btm.insert(sc.p.meta.name.clone(), sc.clone()); |
669 | } | |
670 | ||
671 | let mut first = true; | |
672 | for btm in ord_m.values() { | |
673 | for sc in btm.values() { | |
674 | if first { | |
675 | first = false; | |
676 | } else { | |
3b2f2976 | 677 | self.writer.write_all(b"\n")?; |
8bb4bdeb | 678 | } |
3b2f2976 | 679 | self.write_arg(sc)?; |
8bb4bdeb XL |
680 | } |
681 | } | |
682 | Ok(()) | |
683 | } | |
684 | ||
685 | /// Writes version of a Parser Object to the wrapped stream. | |
686 | fn write_version(&mut self, parser: &Parser) -> io::Result<()> { | |
041b39d2 | 687 | debugln!("Help::write_version;"); |
3b2f2976 | 688 | write!(self.writer, "{}", parser.meta.version.unwrap_or(""))?; |
8bb4bdeb XL |
689 | Ok(()) |
690 | } | |
691 | ||
692 | /// Writes binary name of a Parser Object to the wrapped stream. | |
693 | fn write_bin_name(&mut self, parser: &Parser) -> io::Result<()> { | |
041b39d2 XL |
694 | debugln!("Help::write_bin_name;"); |
695 | macro_rules! write_name { | |
696 | () => {{ | |
697 | let mut name = parser.meta.name.clone(); | |
698 | name = name.replace("{n}", "\n"); | |
3b2f2976 | 699 | color!(self, wrap_help(&name, self.term_w), good)?; |
041b39d2 XL |
700 | }}; |
701 | } | |
8bb4bdeb XL |
702 | if let Some(bn) = parser.meta.bin_name.as_ref() { |
703 | if bn.contains(' ') { | |
704 | // Incase we're dealing with subcommands i.e. git mv is translated to git-mv | |
3b2f2976 | 705 | color!(self, bn.replace(" ", "-"), good)? |
8bb4bdeb | 706 | } else { |
041b39d2 | 707 | write_name!(); |
8bb4bdeb XL |
708 | } |
709 | } else { | |
041b39d2 | 710 | write_name!(); |
8bb4bdeb XL |
711 | } |
712 | Ok(()) | |
713 | } | |
714 | ||
715 | /// Writes default help for a Parser Object to the wrapped stream. | |
716 | pub fn write_default_help(&mut self, parser: &Parser) -> ClapResult<()> { | |
041b39d2 | 717 | debugln!("Help::write_default_help;"); |
8bb4bdeb | 718 | if let Some(h) = parser.meta.pre_help { |
3b2f2976 XL |
719 | self.write_before_after_help(h)?; |
720 | self.writer.write_all(b"\n\n")?; | |
8bb4bdeb XL |
721 | } |
722 | ||
041b39d2 XL |
723 | macro_rules! write_thing { |
724 | ($thing:expr) => {{ | |
725 | let mut owned_thing = $thing.to_owned(); | |
726 | owned_thing = owned_thing.replace("{n}", "\n"); | |
8faf50e0 | 727 | write!(self.writer, "{}\n", wrap_help(&owned_thing, self.term_w))? |
041b39d2 XL |
728 | }}; |
729 | } | |
8bb4bdeb | 730 | // Print the version |
3b2f2976 XL |
731 | self.write_bin_name(parser)?; |
732 | self.writer.write_all(b" ")?; | |
733 | self.write_version(parser)?; | |
734 | self.writer.write_all(b"\n")?; | |
8bb4bdeb | 735 | if let Some(author) = parser.meta.author { |
041b39d2 | 736 | write_thing!(author) |
8bb4bdeb | 737 | } |
8faf50e0 XL |
738 | // if self.use_long { |
739 | // if let Some(about) = parser.meta.long_about { | |
740 | // debugln!("Help::write_default_help: writing long about"); | |
741 | // write_thing!(about) | |
742 | // } else if let Some(about) = parser.meta.about { | |
743 | // debugln!("Help::write_default_help: writing about"); | |
744 | // write_thing!(about) | |
745 | // } | |
746 | // } else | |
747 | if let Some(about) = parser.meta.long_about { | |
748 | debugln!("Help::write_default_help: writing long about"); | |
749 | write_thing!(about) | |
abe05a73 XL |
750 | } else if let Some(about) = parser.meta.about { |
751 | debugln!("Help::write_default_help: writing about"); | |
752 | write_thing!(about) | |
8bb4bdeb XL |
753 | } |
754 | ||
3b2f2976 | 755 | color!(self, "\nUSAGE:", warning)?; |
abe05a73 XL |
756 | write!( |
757 | self.writer, | |
758 | "\n{}{}\n\n", | |
759 | TAB, | |
760 | usage::create_usage_no_title(parser, &[]) | |
761 | )?; | |
8bb4bdeb XL |
762 | |
763 | let flags = parser.has_flags(); | |
764 | let pos = parser.has_positionals(); | |
765 | let opts = parser.has_opts(); | |
766 | let subcmds = parser.has_subcommands(); | |
767 | ||
768 | if flags || opts || pos || subcmds { | |
3b2f2976 | 769 | self.write_all_args(parser)?; |
8bb4bdeb XL |
770 | } |
771 | ||
772 | if let Some(h) = parser.meta.more_help { | |
773 | if flags || opts || pos || subcmds { | |
3b2f2976 | 774 | self.writer.write_all(b"\n\n")?; |
8bb4bdeb | 775 | } |
3b2f2976 | 776 | self.write_before_after_help(h)?; |
8bb4bdeb XL |
777 | } |
778 | ||
779 | self.writer.flush().map_err(Error::from) | |
780 | } | |
781 | } | |
782 | ||
783 | /// Possible results for a copying function that stops when a given | |
784 | /// byte was found. | |
785 | enum CopyUntilResult { | |
786 | DelimiterFound(usize), | |
787 | DelimiterNotFound(usize), | |
788 | ReaderEmpty, | |
789 | ReadError(io::Error), | |
790 | WriteError(io::Error), | |
791 | } | |
792 | ||
793 | /// Copies the contents of a reader into a writer until a delimiter byte is found. | |
794 | /// On success, the total number of bytes that were | |
795 | /// copied from reader to writer is returned. | |
796 | fn copy_until<R: Read, W: Write>(r: &mut R, w: &mut W, delimiter_byte: u8) -> CopyUntilResult { | |
041b39d2 | 797 | debugln!("copy_until;"); |
8bb4bdeb XL |
798 | |
799 | let mut count = 0; | |
800 | for wb in r.bytes() { | |
801 | match wb { | |
802 | Ok(b) => { | |
803 | if b == delimiter_byte { | |
804 | return CopyUntilResult::DelimiterFound(count); | |
805 | } | |
806 | match w.write(&[b]) { | |
807 | Ok(c) => count += c, | |
808 | Err(e) => return CopyUntilResult::WriteError(e), | |
809 | } | |
810 | } | |
811 | Err(e) => return CopyUntilResult::ReadError(e), | |
812 | } | |
813 | } | |
814 | if count > 0 { | |
815 | CopyUntilResult::DelimiterNotFound(count) | |
816 | } else { | |
817 | CopyUntilResult::ReaderEmpty | |
818 | } | |
819 | } | |
820 | ||
821 | /// Copies the contents of a reader into a writer until a {tag} is found, | |
822 | /// copying the tag content to a buffer and returning its size. | |
823 | /// In addition to errors, there are three possible outputs: | |
041b39d2 XL |
824 | /// - `None`: The reader was consumed. |
825 | /// - `Some(Ok(0))`: No tag was captured but the reader still contains data. | |
826 | /// - `Some(Ok(length>0))`: a tag with `length` was captured to the `tag_buffer`. | |
abe05a73 XL |
827 | fn copy_and_capture<R: Read, W: Write>( |
828 | r: &mut R, | |
829 | w: &mut W, | |
830 | tag_buffer: &mut Cursor<Vec<u8>>, | |
831 | ) -> Option<io::Result<usize>> { | |
8bb4bdeb | 832 | use self::CopyUntilResult::*; |
041b39d2 | 833 | debugln!("copy_and_capture;"); |
8bb4bdeb XL |
834 | |
835 | // Find the opening byte. | |
836 | match copy_until(r, w, b'{') { | |
8bb4bdeb XL |
837 | // The end of the reader was reached without finding the opening tag. |
838 | // (either with or without having copied data to the writer) | |
839 | // Return None indicating that we are done. | |
abe05a73 | 840 | ReaderEmpty | DelimiterNotFound(_) => None, |
8bb4bdeb XL |
841 | |
842 | // Something went wrong. | |
843 | ReadError(e) | WriteError(e) => Some(Err(e)), | |
844 | ||
845 | // The opening byte was found. | |
846 | // (either with or without having copied data to the writer) | |
847 | DelimiterFound(_) => { | |
8bb4bdeb XL |
848 | // Lets reset the buffer first and find out how long it is. |
849 | tag_buffer.set_position(0); | |
850 | let buffer_size = tag_buffer.get_ref().len(); | |
851 | ||
852 | // Find the closing byte,limiting the reader to the length of the buffer. | |
853 | let mut rb = r.take(buffer_size as u64); | |
854 | match copy_until(&mut rb, tag_buffer, b'}') { | |
8bb4bdeb XL |
855 | // We were already at the end of the reader. |
856 | // Return None indicating that we are done. | |
857 | ReaderEmpty => None, | |
858 | ||
859 | // The closing tag was found. | |
860 | // Return the tag_length. | |
861 | DelimiterFound(tag_length) => Some(Ok(tag_length)), | |
862 | ||
863 | // The end of the reader was found without finding the closing tag. | |
864 | // Write the opening byte and captured text to the writer. | |
0531ce1d | 865 | // Return 0 indicating that nothing was captured but the reader still contains data. |
abe05a73 XL |
866 | DelimiterNotFound(not_tag_length) => match w.write(b"{") { |
867 | Err(e) => Some(Err(e)), | |
868 | _ => match w.write(&tag_buffer.get_ref()[0..not_tag_length]) { | |
8bb4bdeb | 869 | Err(e) => Some(Err(e)), |
abe05a73 XL |
870 | _ => Some(Ok(0)), |
871 | }, | |
872 | }, | |
8bb4bdeb XL |
873 | |
874 | ReadError(e) | WriteError(e) => Some(Err(e)), | |
875 | } | |
876 | } | |
877 | } | |
878 | } | |
879 | ||
8bb4bdeb XL |
880 | // Methods to write Parser help using templates. |
881 | impl<'a> Help<'a> { | |
882 | /// Write help to stream for the parser in the format defined by the template. | |
883 | /// | |
884 | /// Tags arg given inside curly brackets: | |
885 | /// Valid tags are: | |
886 | /// * `{bin}` - Binary name. | |
887 | /// * `{version}` - Version number. | |
888 | /// * `{author}` - Author information. | |
889 | /// * `{usage}` - Automatically generated or given usage string. | |
890 | /// * `{all-args}` - Help for all arguments (options, flags, positionals arguments, | |
891 | /// and subcommands) including titles. | |
892 | /// * `{unified}` - Unified help for options and flags. | |
893 | /// * `{flags}` - Help for flags. | |
894 | /// * `{options}` - Help for options. | |
895 | /// * `{positionals}` - Help for positionals arguments. | |
896 | /// * `{subcommands}` - Help for subcommands. | |
897 | /// * `{after-help}` - Info to be displayed after the help message. | |
898 | /// * `{before-help}` - Info to be displayed before the help message. | |
899 | /// | |
8faf50e0 | 900 | /// The template system is, on purpose, very simple. Therefore the tags have to written |
8bb4bdeb XL |
901 | /// in the lowercase and without spacing. |
902 | fn write_templated_help(&mut self, parser: &Parser, template: &str) -> ClapResult<()> { | |
041b39d2 | 903 | debugln!("Help::write_templated_help;"); |
8bb4bdeb XL |
904 | let mut tmplr = Cursor::new(&template); |
905 | let mut tag_buf = Cursor::new(vec![0u8; 15]); | |
906 | ||
0531ce1d | 907 | // The strategy is to copy the template from the reader to wrapped stream |
8bb4bdeb XL |
908 | // until a tag is found. Depending on its value, the appropriate content is copied |
909 | // to the wrapped stream. | |
910 | // The copy from template is then resumed, repeating this sequence until reading | |
911 | // the complete template. | |
912 | ||
913 | loop { | |
914 | let tag_length = match copy_and_capture(&mut tmplr, &mut self.writer, &mut tag_buf) { | |
915 | None => return Ok(()), | |
916 | Some(Err(e)) => return Err(Error::from(e)), | |
917 | Some(Ok(val)) if val > 0 => val, | |
918 | _ => continue, | |
919 | }; | |
920 | ||
041b39d2 | 921 | debugln!("Help::write_template_help:iter: tag_buf={};", unsafe { |
abe05a73 XL |
922 | String::from_utf8_unchecked( |
923 | tag_buf.get_ref()[0..tag_length] | |
924 | .iter() | |
925 | .map(|&i| i) | |
926 | .collect::<Vec<_>>(), | |
927 | ) | |
8bb4bdeb XL |
928 | }); |
929 | match &tag_buf.get_ref()[0..tag_length] { | |
930 | b"?" => { | |
3b2f2976 | 931 | self.writer.write_all(b"Could not decode tag name")?; |
8bb4bdeb XL |
932 | } |
933 | b"bin" => { | |
3b2f2976 | 934 | self.write_bin_name(parser)?; |
8bb4bdeb XL |
935 | } |
936 | b"version" => { | |
abe05a73 XL |
937 | write!( |
938 | self.writer, | |
939 | "{}", | |
940 | parser.meta.version.unwrap_or("unknown version") | |
941 | )?; | |
8bb4bdeb XL |
942 | } |
943 | b"author" => { | |
abe05a73 XL |
944 | write!( |
945 | self.writer, | |
946 | "{}", | |
947 | parser.meta.author.unwrap_or("unknown author") | |
948 | )?; | |
8bb4bdeb XL |
949 | } |
950 | b"about" => { | |
abe05a73 XL |
951 | write!( |
952 | self.writer, | |
953 | "{}", | |
954 | parser.meta.about.unwrap_or("unknown about") | |
955 | )?; | |
8bb4bdeb | 956 | } |
ea8adc8c | 957 | b"long-about" => { |
abe05a73 XL |
958 | write!( |
959 | self.writer, | |
960 | "{}", | |
961 | parser.meta.long_about.unwrap_or("unknown about") | |
962 | )?; | |
ea8adc8c | 963 | } |
8bb4bdeb | 964 | b"usage" => { |
3b2f2976 | 965 | write!(self.writer, "{}", usage::create_usage_no_title(parser, &[]))?; |
8bb4bdeb XL |
966 | } |
967 | b"all-args" => { | |
3b2f2976 | 968 | self.write_all_args(parser)?; |
8bb4bdeb XL |
969 | } |
970 | b"unified" => { | |
041b39d2 XL |
971 | let opts_flags = parser |
972 | .flags() | |
8bb4bdeb XL |
973 | .map(as_arg_trait) |
974 | .chain(parser.opts().map(as_arg_trait)); | |
3b2f2976 | 975 | self.write_args(opts_flags)?; |
8bb4bdeb XL |
976 | } |
977 | b"flags" => { | |
3b2f2976 | 978 | self.write_args(parser.flags().map(as_arg_trait))?; |
8bb4bdeb XL |
979 | } |
980 | b"options" => { | |
3b2f2976 | 981 | self.write_args(parser.opts().map(as_arg_trait))?; |
8bb4bdeb XL |
982 | } |
983 | b"positionals" => { | |
3b2f2976 | 984 | self.write_args(parser.positionals().map(as_arg_trait))?; |
8bb4bdeb XL |
985 | } |
986 | b"subcommands" => { | |
3b2f2976 | 987 | self.write_subcommands(parser)?; |
8bb4bdeb XL |
988 | } |
989 | b"after-help" => { | |
abe05a73 XL |
990 | write!( |
991 | self.writer, | |
992 | "{}", | |
993 | parser.meta.more_help.unwrap_or("unknown after-help") | |
994 | )?; | |
8bb4bdeb XL |
995 | } |
996 | b"before-help" => { | |
abe05a73 XL |
997 | write!( |
998 | self.writer, | |
999 | "{}", | |
1000 | parser.meta.pre_help.unwrap_or("unknown before-help") | |
1001 | )?; | |
8bb4bdeb XL |
1002 | } |
1003 | // Unknown tag, write it back. | |
1004 | r => { | |
3b2f2976 XL |
1005 | self.writer.write_all(b"{")?; |
1006 | self.writer.write_all(r)?; | |
1007 | self.writer.write_all(b"}")?; | |
8bb4bdeb XL |
1008 | } |
1009 | } | |
1010 | } | |
1011 | } | |
1012 | } | |
1013 | ||
041b39d2 XL |
1014 | fn wrap_help(help: &str, avail_chars: usize) -> String { |
1015 | let wrapper = textwrap::Wrapper::new(avail_chars).break_words(false); | |
1016 | help.lines() | |
1017 | .map(|line| wrapper.fill(line)) | |
1018 | .collect::<Vec<String>>() | |
1019 | .join("\n") | |
1020 | } | |
1021 | ||
1022 | #[cfg(test)] | |
1023 | mod test { | |
1024 | use super::wrap_help; | |
1025 | ||
1026 | #[test] | |
1027 | fn wrap_help_last_word() { | |
1028 | let help = String::from("foo bar baz"); | |
1029 | assert_eq!(wrap_help(&help, 5), "foo\nbar\nbaz"); | |
8bb4bdeb XL |
1030 | } |
1031 | } |