1use std::error;
7use std::ffi::{OsStr, OsString};
8use std::fmt;
9
10const WIDTH: usize = 80;
14
15const PADDING: usize = 2;
19
20const MAX_USAGE: usize = 24;
24
25type BoxError = Box<dyn error::Error + Send + Sync + 'static>;
27
28#[doc(hidden)]
32#[inline]
33pub fn into_result<T>(value: T) -> Result<(), BoxError>
34where
35 T: IntoResult,
36{
37 value.into_result()
38}
39
40#[doc(hidden)]
41pub trait IntoResult {
42 fn into_result(self) -> Result<(), BoxError>;
43}
44
45impl IntoResult for () {
46 #[inline]
47 fn into_result(self) -> Result<(), BoxError> {
48 Ok(())
49 }
50}
51
52impl<E> IntoResult for Result<(), E>
53where
54 BoxError: From<E>,
55{
56 #[inline]
57 fn into_result(self) -> Result<(), BoxError> {
58 Ok(self?)
59 }
60}
61
62pub struct Switch {
64 pub usage: &'static str,
68 pub docs: &'static [&'static str],
70}
71
72pub struct Help {
74 pub usage: &'static str,
76 pub docs: &'static [&'static str],
78 pub switches: &'static [Switch],
80}
81
82impl Help {
83 pub fn format(&self) -> HelpFormat {
113 HelpFormat {
114 help: self,
115 width: WIDTH,
116 padding: PADDING,
117 max_usage: MAX_USAGE,
118 }
119 }
120}
121
122impl fmt::Display for Help {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 self.format().fmt(f)
125 }
126}
127
128pub struct HelpFormat<'a> {
132 help: &'a Help,
133 width: usize,
134 padding: usize,
135 max_usage: usize,
136}
137
138impl HelpFormat<'_> {
139 pub fn width(self, width: usize) -> Self {
141 Self { width, ..self }
142 }
143
144 pub fn padding(self, padding: usize) -> Self {
149 Self { padding, ..self }
150 }
151
152 pub fn max_usage(self, max_usage: usize) -> Self {
159 Self { max_usage, ..self }
160 }
161}
162
163impl<'a> fmt::Display for HelpFormat<'a> {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 writeln!(f, "Usage: {name}", name = self.help.usage)?;
166
167 if !self.help.docs.is_empty() {
168 writeln!(f, "{}", TextWrap::new("", self.help.docs, self.width, 0))?;
169 }
170
171 writeln!(f)?;
172
173 let usage_len = self
174 .help
175 .switches
176 .iter()
177 .map(|s| {
178 usize::min(
179 self.max_usage,
180 if s.docs.is_empty() {
181 s.usage.len()
182 } else {
183 s.usage.len() + self.padding
184 },
185 )
186 })
187 .max()
188 .unwrap_or(self.max_usage);
189
190 if !self.help.switches.is_empty() {
191 writeln!(f, "Options:")?;
192 let mut first = true;
193
194 let mut it = self.help.switches.iter().peekable();
195
196 while let Some(d) = it.next() {
197 let first = std::mem::take(&mut first);
198 let more = it.peek().is_some();
199
200 let wrap = TextWrap {
201 init: d.usage,
202 docs: d.docs,
203 width: self.width,
204 padding: self.padding,
205 init_len: Some(usage_len),
206 first,
207 more,
208 };
209
210 writeln!(f, "{}", wrap)?;
211 }
212 }
213
214 Ok(())
215 }
216}
217
218struct TextWrap<'a> {
220 init: &'a str,
221 docs: &'a [&'static str],
222 width: usize,
223 padding: usize,
224 init_len: Option<usize>,
226 first: bool,
228 more: bool,
230}
231
232impl<'a> TextWrap<'a> {
233 fn new(init: &'a str, docs: &'a [&'static str], width: usize, padding: usize) -> Self {
234 Self {
235 init,
236 docs,
237 width,
238 padding,
239 init_len: None,
240 first: true,
241 more: false,
242 }
243 }
244
245 fn wrap(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 let mut it = self.docs.iter().peekable();
247
248 if it.peek().is_none() {
250 fill_spaces(f, self.padding)?;
251 f.write_str(self.init)?;
252 return Ok(());
253 }
254
255 let init_len = self.init_len.unwrap_or(self.init.len());
256
257 let (long, mut init) = if self.init.len() + self.padding > init_len {
258 (true, None)
259 } else {
260 (false, Some(&self.init))
261 };
262
263 if long {
265 if !self.first {
268 writeln!(f)?;
269 }
270
271 fill_spaces(f, self.padding)?;
272 writeln!(f, "{}", self.init)?;
273 }
274
275 let fill = init_len + self.padding;
276
277 let trim = it.peek().map(|line| chars_count(line, |c| c == ' '));
278
279 while let Some(line) = it.next() {
280 let mut line = *line;
281
282 if let Some(trim) = trim {
284 line = skip_chars(line, trim);
285
286 if line.is_empty() {
288 writeln!(f)?;
289 continue;
290 }
291 }
292
293 let ws_fill = next_index(line, char::is_alphanumeric).unwrap_or_default();
295 let mut line_first = true;
296
297 loop {
298 let fill = if !std::mem::take(&mut line_first) {
299 fill + ws_fill
300 } else {
301 fill
302 };
303
304 let mut space_span = None;
305
306 loop {
307 let c = space_span.map(|(_, e)| e).unwrap_or_default();
308
309 let (start, leap) = match line[c..].find(' ') {
310 Some(i) => {
311 let leap = next_index(&line[c + i..], |c| c != ' ').unwrap_or(1);
312 (c + i, leap)
313 }
314 None => {
315 if line.len() + fill <= self.width {
318 space_span = None;
319 }
320
321 break;
322 }
323 };
324
325 if start + fill > self.width {
326 break;
327 }
328
329 space_span = Some((start, start + leap));
330 }
331
332 let init_len = if let Some(init) = init.take() {
333 fill_spaces(f, self.padding)?;
334 f.write_str(init)?;
335 self.padding + init.len()
336 } else {
337 0
338 };
339
340 fill_spaces(f, fill.saturating_sub(init_len))?;
341
342 if let Some((start, end)) = space_span {
343 writeln!(f, "{}", &line[..start])?;
344 line = &line[end..];
345 continue;
346 }
347
348 f.write_str(line)?;
349 break;
350 }
351
352 if it.peek().is_some() {
353 writeln!(f)?;
354 }
355 }
356
357 if long && !self.first && self.more {
360 writeln!(f)?;
361 }
362
363 return Ok(());
364
365 fn next_index(s: &str, p: fn(char) -> bool) -> Option<usize> {
367 Some(s.char_indices().find(|&(_, c)| p(c))?.0)
368 }
369
370 fn chars_count(s: &str, p: fn(char) -> bool) -> usize {
372 s.chars().take_while(|c| p(*c)).count()
373 }
374
375 fn skip_chars(s: &str, count: usize) -> &str {
377 let e = s
378 .char_indices()
379 .skip(count)
380 .map(|(i, _)| i)
381 .next()
382 .unwrap_or(s.len());
383
384 &s[e..]
385 }
386
387 fn fill_spaces(f: &mut fmt::Formatter<'_>, mut count: usize) -> fmt::Result {
388 static BUF: &str = " ";
390
391 while count > 0 {
392 f.write_str(&BUF[..usize::min(count, BUF.len())])?;
393 count = count.saturating_sub(BUF.len());
394 }
395
396 Ok(())
397 }
398 }
399}
400
401impl fmt::Display for TextWrap<'_> {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 self.wrap(f)
404 }
405}
406
407pub struct Input<I>
409where
410 I: Iterator,
411{
412 it: I,
413 buf: Option<I::Item>,
414}
415
416impl<I> Input<I>
417where
418 I: Iterator,
419{
420 pub fn new(it: I) -> Self {
422 Self { it, buf: None }
423 }
424}
425
426impl<I> Input<I>
427where
428 I: Iterator,
429 I::Item: TryIntoInput,
430{
431 #[allow(clippy::should_implement_trait)]
436 pub fn next(&mut self) -> Result<Option<String>, InputError> {
437 if let Some(item) = self.buf.take() {
438 return Ok(Some(item.try_into_string()?));
439 }
440
441 let item = match self.it.next() {
442 Some(item) => item,
443 None => return Ok(None),
444 };
445
446 Ok(Some(item.try_into_string()?))
447 }
448
449 pub fn next_os(&mut self) -> Option<OsString> {
451 if let Some(item) = self.buf.take() {
452 return Some(item.into_os_string());
453 }
454
455 let item = match self.it.next() {
456 Some(item) => item,
457 None => return None,
458 };
459
460 Some(item.into_os_string())
461 }
462
463 pub fn next_unless_switch(&mut self) -> Result<Option<String>, InputError> {
465 match self.peek() {
466 Some(s) if s.starts_with('-') => Ok(None),
467 _ => self.next(),
468 }
469 }
470
471 pub fn next_unless_switch_os(&mut self) -> Option<OsString> {
473 match self.peek() {
474 Some(s) if s.starts_with('-') => None,
475 _ => self.next_os(),
476 }
477 }
478
479 pub fn rest(&mut self) -> Result<Vec<String>, InputError> {
481 let mut buf = Vec::new();
482
483 if let Some(item) = self.buf.take() {
484 buf.push(item.try_into_string()?);
485 }
486
487 for item in &mut self.it {
488 buf.push(item.try_into_string()?);
489 }
490
491 Ok(buf)
492 }
493
494 pub fn rest_os(&mut self) -> Vec<OsString> {
496 let mut buf = Vec::new();
497
498 if let Some(item) = self.buf.take() {
499 buf.push(item.into_os_string());
500 }
501
502 for item in &mut self.it {
503 buf.push(item.into_os_string());
504 }
505
506 buf
507 }
508
509 fn peek(&mut self) -> Option<&str> {
510 if self.buf.is_none() {
511 self.buf = self.it.next();
512 }
513
514 let item = match self.buf.as_ref() {
515 Some(item) => item,
516 None => return None,
517 };
518
519 item.try_as_str().ok()
520 }
521}
522
523#[derive(Debug)]
524pub struct InputError(());
525
526impl fmt::Display for InputError {
527 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528 write!(f, "encounted non-valid unicode in input")
529 }
530}
531
532impl error::Error for InputError {}
533
534pub trait TryIntoInput: self::internal::Sealed {
542 #[doc(hidden)]
543 fn try_as_str(&self) -> Result<&str, InputError>;
544
545 #[doc(hidden)]
546 fn try_into_string(self) -> Result<String, InputError>;
547
548 #[doc(hidden)]
549 fn into_os_string(self) -> OsString;
550}
551
552impl TryIntoInput for String {
553 #[inline]
554 fn try_as_str(&self) -> Result<&str, InputError> {
555 Ok(self.as_str())
556 }
557
558 #[inline]
559 fn try_into_string(self) -> Result<String, InputError> {
560 Ok(self)
561 }
562
563 #[inline]
564 fn into_os_string(self) -> OsString {
565 OsString::from(self)
566 }
567}
568
569impl TryIntoInput for &str {
570 #[inline]
571 fn try_as_str(&self) -> Result<&str, InputError> {
572 Ok(*self)
573 }
574
575 #[inline]
576 fn try_into_string(self) -> Result<String, InputError> {
577 Ok(self.to_owned())
578 }
579
580 #[inline]
581 fn into_os_string(self) -> OsString {
582 OsString::from(self)
583 }
584}
585
586impl TryIntoInput for OsString {
587 #[inline]
588 fn try_as_str(&self) -> Result<&str, InputError> {
589 self.to_str().ok_or(InputError(()))
590 }
591
592 #[inline]
593 fn try_into_string(self) -> Result<String, InputError> {
594 self.into_string().map_err(|_| InputError(()))
595 }
596
597 #[inline]
598 fn into_os_string(self) -> OsString {
599 self
600 }
601}
602
603impl TryIntoInput for &OsStr {
604 #[inline]
605 fn try_as_str(&self) -> Result<&str, InputError> {
606 self.to_str().ok_or(InputError(()))
607 }
608
609 #[inline]
610 fn try_into_string(self) -> Result<String, InputError> {
611 Ok(self.to_str().ok_or(InputError(()))?.to_owned())
612 }
613
614 #[inline]
615 fn into_os_string(self) -> OsString {
616 self.to_owned()
617 }
618}
619
620mod internal {
621 pub trait Sealed {}
622
623 impl<T> Sealed for T where T: super::TryIntoInput {}
624}