1use alloc::boxed::Box;
7use alloc::string::String;
8use alloc::vec::Vec;
9use core::fmt;
10
11const WIDTH: usize = 80;
15
16const PADDING: usize = 2;
20
21const MAX_USAGE: usize = 24;
25
26type BoxError = Box<dyn core_error::Error + Send + Sync + 'static>;
28
29#[doc(hidden)]
33#[inline]
34pub fn into_result<T>(value: T) -> Result<(), BoxError>
35where
36 T: IntoResult,
37{
38 value.into_result()
39}
40
41#[doc(hidden)]
42pub trait IntoResult {
43 fn into_result(self) -> Result<(), BoxError>;
44}
45
46impl IntoResult for () {
47 #[inline]
48 fn into_result(self) -> Result<(), BoxError> {
49 Ok(())
50 }
51}
52
53impl<E> IntoResult for Result<(), E>
54where
55 BoxError: From<E>,
56{
57 #[inline]
58 fn into_result(self) -> Result<(), BoxError> {
59 Ok(self?)
60 }
61}
62
63pub struct Switch {
65 pub usage: &'static str,
69 pub docs: &'static [&'static str],
71}
72
73pub struct Help {
75 pub usage: &'static str,
77 pub docs: &'static [&'static str],
79 pub switches: &'static [Switch],
81}
82
83impl Help {
84 pub fn format(&self) -> HelpFormat {
114 HelpFormat {
115 help: self,
116 width: WIDTH,
117 padding: PADDING,
118 max_usage: MAX_USAGE,
119 }
120 }
121}
122
123impl fmt::Display for Help {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 self.format().fmt(f)
126 }
127}
128
129pub struct HelpFormat<'a> {
133 help: &'a Help,
134 width: usize,
135 padding: usize,
136 max_usage: usize,
137}
138
139impl HelpFormat<'_> {
140 pub fn width(self, width: usize) -> Self {
142 Self { width, ..self }
143 }
144
145 pub fn padding(self, padding: usize) -> Self {
150 Self { padding, ..self }
151 }
152
153 pub fn max_usage(self, max_usage: usize) -> Self {
160 Self { max_usage, ..self }
161 }
162}
163
164impl<'a> fmt::Display for HelpFormat<'a> {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 writeln!(f, "Usage: {name}", name = self.help.usage)?;
167
168 if !self.help.docs.is_empty() {
169 writeln!(f, "{}", TextWrap::new("", self.help.docs, self.width, 0))?;
170 }
171
172 writeln!(f)?;
173
174 let usage_len = self
175 .help
176 .switches
177 .iter()
178 .map(|s| {
179 usize::min(
180 self.max_usage,
181 if s.docs.is_empty() {
182 s.usage.len()
183 } else {
184 s.usage.len() + self.padding
185 },
186 )
187 })
188 .max()
189 .unwrap_or(self.max_usage);
190
191 if !self.help.switches.is_empty() {
192 writeln!(f, "Options:")?;
193 let mut first = true;
194
195 let mut it = self.help.switches.iter().peekable();
196
197 while let Some(d) = it.next() {
198 let first = core::mem::take(&mut first);
199 let more = it.peek().is_some();
200
201 let wrap = TextWrap {
202 init: d.usage,
203 docs: d.docs,
204 width: self.width,
205 padding: self.padding,
206 init_len: Some(usage_len),
207 first,
208 more,
209 };
210
211 writeln!(f, "{}", wrap)?;
212 }
213 }
214
215 Ok(())
216 }
217}
218
219struct TextWrap<'a> {
221 init: &'a str,
222 docs: &'a [&'static str],
223 width: usize,
224 padding: usize,
225 init_len: Option<usize>,
227 first: bool,
229 more: bool,
231}
232
233impl<'a> TextWrap<'a> {
234 fn new(init: &'a str, docs: &'a [&'static str], width: usize, padding: usize) -> Self {
235 Self {
236 init,
237 docs,
238 width,
239 padding,
240 init_len: None,
241 first: true,
242 more: false,
243 }
244 }
245
246 fn wrap(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 let mut it = self.docs.iter().peekable();
248
249 if it.peek().is_none() {
251 fill_spaces(f, self.padding)?;
252 f.write_str(self.init)?;
253 return Ok(());
254 }
255
256 let init_len = self.init_len.unwrap_or(self.init.len());
257
258 let (long, mut init) = if self.init.len() + self.padding > init_len {
259 (true, None)
260 } else {
261 (false, Some(&self.init))
262 };
263
264 if long {
266 if !self.first {
269 writeln!(f)?;
270 }
271
272 fill_spaces(f, self.padding)?;
273 writeln!(f, "{}", self.init)?;
274 }
275
276 let fill = init_len + self.padding;
277
278 let trim = it.peek().map(|line| chars_count(line, |c| c == ' '));
279
280 while let Some(line) = it.next() {
281 let mut line = *line;
282
283 if let Some(trim) = trim {
285 line = skip_chars(line, trim);
286
287 if line.is_empty() {
289 writeln!(f)?;
290 continue;
291 }
292 }
293
294 let ws_fill = next_index(line, char::is_alphanumeric).unwrap_or_default();
296 let mut line_first = true;
297
298 loop {
299 let fill = if !core::mem::take(&mut line_first) {
300 fill + ws_fill
301 } else {
302 fill
303 };
304
305 let mut space_span = None;
306
307 loop {
308 let c = space_span.map(|(_, e)| e).unwrap_or_default();
309
310 let (start, leap) = match line[c..].find(' ') {
311 Some(i) => {
312 let leap = next_index(&line[c + i..], |c| c != ' ').unwrap_or(1);
313 (c + i, leap)
314 }
315 None => {
316 if line.len() + fill <= self.width {
319 space_span = None;
320 }
321
322 break;
323 }
324 };
325
326 if start + fill > self.width {
327 break;
328 }
329
330 space_span = Some((start, start + leap));
331 }
332
333 let init_len = if let Some(init) = init.take() {
334 fill_spaces(f, self.padding)?;
335 f.write_str(init)?;
336 self.padding + init.len()
337 } else {
338 0
339 };
340
341 fill_spaces(f, fill.saturating_sub(init_len))?;
342
343 if let Some((start, end)) = space_span {
344 writeln!(f, "{}", &line[..start])?;
345 line = &line[end..];
346 continue;
347 }
348
349 f.write_str(line)?;
350 break;
351 }
352
353 if it.peek().is_some() {
354 writeln!(f)?;
355 }
356 }
357
358 if long && !self.first && self.more {
361 writeln!(f)?;
362 }
363
364 return Ok(());
365
366 fn next_index(s: &str, p: fn(char) -> bool) -> Option<usize> {
368 Some(s.char_indices().find(|&(_, c)| p(c))?.0)
369 }
370
371 fn chars_count(s: &str, p: fn(char) -> bool) -> usize {
373 s.chars().take_while(|c| p(*c)).count()
374 }
375
376 fn skip_chars(s: &str, count: usize) -> &str {
378 let e = s
379 .char_indices()
380 .skip(count)
381 .map(|(i, _)| i)
382 .next()
383 .unwrap_or(s.len());
384
385 &s[e..]
386 }
387
388 fn fill_spaces(f: &mut fmt::Formatter<'_>, mut count: usize) -> fmt::Result {
389 static BUF: &str = " ";
391
392 while count > 0 {
393 f.write_str(&BUF[..usize::min(count, BUF.len())])?;
394 count = count.saturating_sub(BUF.len());
395 }
396
397 Ok(())
398 }
399 }
400}
401
402impl fmt::Display for TextWrap<'_> {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 self.wrap(f)
405 }
406}
407
408pub struct Input<I>
410where
411 I: Iterator,
412{
413 it: I,
414 buf: Option<I::Item>,
415}
416
417impl<I> Input<I>
418where
419 I: Iterator,
420{
421 pub fn new(it: I) -> Self {
423 Self { it, buf: None }
424 }
425}
426
427impl<I> Input<I>
428where
429 I: Iterator,
430 I::Item: TryIntoInput,
431{
432 #[allow(clippy::should_implement_trait)]
437 pub fn next(&mut self) -> Result<Option<String>, InputError> {
438 if let Some(item) = self.buf.take() {
439 return Ok(Some(item.try_into_string()?));
440 }
441
442 let item = match self.it.next() {
443 Some(item) => item,
444 None => return Ok(None),
445 };
446
447 Ok(Some(item.try_into_string()?))
448 }
449
450 pub fn next_unless_switch(&mut self) -> Result<Option<String>, InputError> {
452 match self.peek() {
453 Some(s) if s.starts_with('-') => Ok(None),
454 _ => self.next(),
455 }
456 }
457
458 pub fn rest(&mut self) -> Result<Vec<String>, InputError> {
460 let mut buf = Vec::new();
461
462 if let Some(item) = self.buf.take() {
463 buf.push(item.try_into_string()?);
464 }
465
466 for item in &mut self.it {
467 buf.push(item.try_into_string()?);
468 }
469
470 Ok(buf)
471 }
472
473 fn peek(&mut self) -> Option<&str> {
474 if self.buf.is_none() {
475 self.buf = self.it.next();
476 }
477
478 let item = match self.buf.as_ref() {
479 Some(item) => item,
480 None => return None,
481 };
482
483 item.try_as_str().ok()
484 }
485}
486
487#[derive(Debug)]
488pub struct InputError(());
489
490impl fmt::Display for InputError {
491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492 write!(f, "encounted non-valid unicode in input")
493 }
494}
495
496impl core_error::Error for InputError {}
497
498pub trait TryIntoInput: self::internal::Sealed {
506 #[doc(hidden)]
507 fn try_as_str(&self) -> Result<&str, InputError>;
508
509 #[doc(hidden)]
510 fn try_into_string(self) -> Result<String, InputError>;
511}
512
513impl TryIntoInput for String {
514 #[inline]
515 fn try_as_str(&self) -> Result<&str, InputError> {
516 Ok(self.as_str())
517 }
518
519 #[inline]
520 fn try_into_string(self) -> Result<String, InputError> {
521 Ok(self)
522 }
523}
524
525impl TryIntoInput for &str {
526 #[inline]
527 fn try_as_str(&self) -> Result<&str, InputError> {
528 Ok(*self)
529 }
530
531 #[inline]
532 fn try_into_string(self) -> Result<String, InputError> {
533 Ok(String::from(self))
534 }
535}
536
537mod internal {
538 pub trait Sealed {}
539
540 impl<T> Sealed for T where T: super::TryIntoInput {}
541}