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