use std::error;
use std::ffi::{OsStr, OsString};
use std::fmt;
const WIDTH: usize = 80;
const PADDING: usize = 2;
const MAX_USAGE: usize = 24;
type BoxError = Box<dyn error::Error + Send + Sync + 'static>;
#[doc(hidden)]
#[inline]
pub fn into_result<T>(value: T) -> Result<(), BoxError>
where
T: IntoResult,
{
value.into_result()
}
#[doc(hidden)]
pub trait IntoResult {
fn into_result(self) -> Result<(), BoxError>;
}
impl IntoResult for () {
#[inline]
fn into_result(self) -> Result<(), BoxError> {
Ok(())
}
}
impl<E> IntoResult for Result<(), E>
where
BoxError: From<E>,
{
#[inline]
fn into_result(self) -> Result<(), BoxError> {
Ok(self?)
}
}
pub struct Switch {
pub usage: &'static str,
pub docs: &'static [&'static str],
}
pub struct Help {
pub usage: &'static str,
pub docs: &'static [&'static str],
pub switches: &'static [Switch],
}
impl Help {
pub fn format(&self) -> HelpFormat {
HelpFormat {
help: self,
width: WIDTH,
padding: PADDING,
max_usage: MAX_USAGE,
}
}
}
impl fmt::Display for Help {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.format().fmt(f)
}
}
pub struct HelpFormat<'a> {
help: &'a Help,
width: usize,
padding: usize,
max_usage: usize,
}
impl HelpFormat<'_> {
pub fn width(self, width: usize) -> Self {
Self { width, ..self }
}
pub fn padding(self, padding: usize) -> Self {
Self { padding, ..self }
}
pub fn max_usage(self, max_usage: usize) -> Self {
Self { max_usage, ..self }
}
}
impl<'a> fmt::Display for HelpFormat<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Usage: {name}", name = self.help.usage)?;
if !self.help.docs.is_empty() {
writeln!(f, "{}", TextWrap::new("", self.help.docs, self.width, 0))?;
}
writeln!(f)?;
let usage_len = self
.help
.switches
.iter()
.map(|s| {
usize::min(
self.max_usage,
if s.docs.is_empty() {
s.usage.len()
} else {
s.usage.len() + self.padding
},
)
})
.max()
.unwrap_or(self.max_usage);
if !self.help.switches.is_empty() {
writeln!(f, "Options:")?;
let mut first = true;
let mut it = self.help.switches.iter().peekable();
while let Some(d) = it.next() {
let first = std::mem::take(&mut first);
let more = it.peek().is_some();
let wrap = TextWrap {
init: d.usage,
docs: d.docs,
width: self.width,
padding: self.padding,
init_len: Some(usage_len),
first,
more,
};
writeln!(f, "{}", wrap)?;
}
}
Ok(())
}
}
struct TextWrap<'a> {
init: &'a str,
docs: &'a [&'static str],
width: usize,
padding: usize,
init_len: Option<usize>,
first: bool,
more: bool,
}
impl<'a> TextWrap<'a> {
fn new(init: &'a str, docs: &'a [&'static str], width: usize, padding: usize) -> Self {
Self {
init,
docs,
width,
padding,
init_len: None,
first: true,
more: false,
}
}
fn wrap(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut it = self.docs.iter().peekable();
if it.peek().is_none() {
fill_spaces(f, self.padding)?;
f.write_str(self.init)?;
return Ok(());
}
let init_len = self.init_len.unwrap_or(self.init.len());
let (long, mut init) = if self.init.len() + self.padding > init_len {
(true, None)
} else {
(false, Some(&self.init))
};
if long {
if !self.first {
writeln!(f)?;
}
fill_spaces(f, self.padding)?;
writeln!(f, "{}", self.init)?;
}
let fill = init_len + self.padding;
let trim = it.peek().map(|line| chars_count(line, |c| c == ' '));
while let Some(line) = it.next() {
let mut line = *line;
if let Some(trim) = trim {
line = skip_chars(line, trim);
if line.is_empty() {
writeln!(f)?;
continue;
}
}
let ws_fill = next_index(line, char::is_alphanumeric).unwrap_or_default();
let mut line_first = true;
loop {
let fill = if !std::mem::take(&mut line_first) {
fill + ws_fill
} else {
fill
};
let mut space_span = None;
loop {
let c = space_span.map(|(_, e)| e).unwrap_or_default();
let (start, leap) = match line[c..].find(' ') {
Some(i) => {
let leap = next_index(&line[c + i..], |c| c != ' ').unwrap_or(1);
(c + i, leap)
}
None => {
if line.len() + fill <= self.width {
space_span = None;
}
break;
}
};
if start + fill > self.width {
break;
}
space_span = Some((start, start + leap));
}
let init_len = if let Some(init) = init.take() {
fill_spaces(f, self.padding)?;
f.write_str(init)?;
self.padding + init.len()
} else {
0
};
fill_spaces(f, fill.saturating_sub(init_len))?;
if let Some((start, end)) = space_span {
writeln!(f, "{}", &line[..start])?;
line = &line[end..];
continue;
}
f.write_str(line)?;
break;
}
if it.peek().is_some() {
writeln!(f)?;
}
}
if long && !self.first && self.more {
writeln!(f)?;
}
return Ok(());
fn next_index(s: &str, p: fn(char) -> bool) -> Option<usize> {
Some(s.char_indices().find(|&(_, c)| p(c))?.0)
}
fn chars_count(s: &str, p: fn(char) -> bool) -> usize {
s.chars().take_while(|c| p(*c)).count()
}
fn skip_chars(s: &str, count: usize) -> &str {
let e = s
.char_indices()
.skip(count)
.map(|(i, _)| i)
.next()
.unwrap_or(s.len());
&s[e..]
}
fn fill_spaces(f: &mut fmt::Formatter<'_>, mut count: usize) -> fmt::Result {
static BUF: &str = " ";
while count > 0 {
f.write_str(&BUF[..usize::min(count, BUF.len())])?;
count = count.saturating_sub(BUF.len());
}
Ok(())
}
}
}
impl fmt::Display for TextWrap<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.wrap(f)
}
}
pub struct Input<I>
where
I: Iterator,
{
it: I,
buf: Option<I::Item>,
}
impl<I> Input<I>
where
I: Iterator,
{
pub fn new(it: I) -> Self {
Self { it, buf: None }
}
}
impl<I> Input<I>
where
I: Iterator,
I::Item: TryIntoInput,
{
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Option<String>, InputError> {
if let Some(item) = self.buf.take() {
return Ok(Some(item.try_into_string()?));
}
let item = match self.it.next() {
Some(item) => item,
None => return Ok(None),
};
Ok(Some(item.try_into_string()?))
}
pub fn next_os(&mut self) -> Option<OsString> {
if let Some(item) = self.buf.take() {
return Some(item.into_os_string());
}
let item = match self.it.next() {
Some(item) => item,
None => return None,
};
Some(item.into_os_string())
}
pub fn next_unless_switch(&mut self) -> Result<Option<String>, InputError> {
match self.peek() {
Some(s) if s.starts_with('-') => Ok(None),
_ => self.next(),
}
}
pub fn next_unless_switch_os(&mut self) -> Option<OsString> {
match self.peek() {
Some(s) if s.starts_with('-') => None,
_ => self.next_os(),
}
}
pub fn rest(&mut self) -> Result<Vec<String>, InputError> {
let mut buf = Vec::new();
if let Some(item) = self.buf.take() {
buf.push(item.try_into_string()?);
}
for item in &mut self.it {
buf.push(item.try_into_string()?);
}
Ok(buf)
}
pub fn rest_os(&mut self) -> Vec<OsString> {
let mut buf = Vec::new();
if let Some(item) = self.buf.take() {
buf.push(item.into_os_string());
}
for item in &mut self.it {
buf.push(item.into_os_string());
}
buf
}
fn peek(&mut self) -> Option<&str> {
if self.buf.is_none() {
self.buf = self.it.next();
}
let item = match self.buf.as_ref() {
Some(item) => item,
None => return None,
};
item.try_as_str().ok()
}
}
#[derive(Debug)]
pub struct InputError(());
impl fmt::Display for InputError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "encounted non-valid unicode in input")
}
}
impl error::Error for InputError {}
pub trait TryIntoInput: self::internal::Sealed {
#[doc(hidden)]
fn try_as_str(&self) -> Result<&str, InputError>;
#[doc(hidden)]
fn try_into_string(self) -> Result<String, InputError>;
#[doc(hidden)]
fn into_os_string(self) -> OsString;
}
impl TryIntoInput for String {
#[inline]
fn try_as_str(&self) -> Result<&str, InputError> {
Ok(self.as_str())
}
#[inline]
fn try_into_string(self) -> Result<String, InputError> {
Ok(self)
}
#[inline]
fn into_os_string(self) -> OsString {
OsString::from(self)
}
}
impl TryIntoInput for &str {
#[inline]
fn try_as_str(&self) -> Result<&str, InputError> {
Ok(*self)
}
#[inline]
fn try_into_string(self) -> Result<String, InputError> {
Ok(self.to_owned())
}
#[inline]
fn into_os_string(self) -> OsString {
OsString::from(self)
}
}
impl TryIntoInput for OsString {
#[inline]
fn try_as_str(&self) -> Result<&str, InputError> {
self.to_str().ok_or(InputError(()))
}
#[inline]
fn try_into_string(self) -> Result<String, InputError> {
self.into_string().map_err(|_| InputError(()))
}
#[inline]
fn into_os_string(self) -> OsString {
self
}
}
impl TryIntoInput for &OsStr {
#[inline]
fn try_as_str(&self) -> Result<&str, InputError> {
self.to_str().ok_or(InputError(()))
}
#[inline]
fn try_into_string(self) -> Result<String, InputError> {
Ok(self.to_str().ok_or(InputError(()))?.to_owned())
}
#[inline]
fn into_os_string(self) -> OsString {
self.to_owned()
}
}
mod internal {
pub trait Sealed {}
impl<T> Sealed for T where T: super::TryIntoInput {}
}