use super::*;
use indent::{indent_all_by, indent_by};
pub const DEFAULT_INDENT_SPACES: usize = 4;
#[must_use]
pub fn indent_string<S: AsRef<str>>(s: S) -> String {
indent_by(DEFAULT_INDENT_SPACES, s.as_ref().to_owned())
}
#[must_use]
pub fn indent_all_string<S: AsRef<str>>(s: S) -> String {
indent_all_by(DEFAULT_INDENT_SPACES, s.as_ref().to_owned())
}
pub trait StripTrailingNewline {
fn strip_trailing_newline(&self) -> &str;
}
impl<T: AsRef<str>> StripTrailingNewline for T {
fn strip_trailing_newline(&self) -> &str {
self.as_ref()
.strip_suffix("\r\n")
.or_else(|| self.as_ref().strip_suffix('\n'))
.unwrap_or(self.as_ref())
}
}
#[must_use]
pub fn human_byte_count<T: Into<u64>>(count: T) -> String {
let count = count.into();
if count >= 1024u64 * 1024u64 * 1024u64 {
format!("{:.3}GB", (count / (1024u64 * 1024u64)) as f64 / 1024.0)
} else if count >= 1024u64 * 1024u64 {
format!("{:.3}MB", (count / 1024u64) as f64 / 1024.0)
} else if count >= 1024u64 {
format!("{:.3}KB", count as f64 / 1024.0)
} else {
format!("{}B", count)
}
}
#[must_use]
pub fn human_byte_data<T: AsRef<[u8]>>(data: T, truncate_len: Option<usize>) -> String {
let data = data.as_ref();
let mut printable = true;
for c in data {
if *c < 32 || *c > 126 {
printable = false;
break;
}
}
let (data, truncated) = if let Some(truncate_len) = truncate_len {
if data.len() > truncate_len {
(&data[0..truncate_len], true)
} else {
(data, false)
}
} else {
(data, false)
};
let strdata = if printable {
String::from_utf8_lossy(data).to_string()
} else {
let sw = shell_words::quote(String::from_utf8_lossy(data).as_ref()).to_string();
let always_hex = sw
.chars()
.any(|c| !c.is_ascii() || c.is_ascii_control() || c.is_ascii_graphic());
let h = hex::encode(data);
if always_hex || h.len() < sw.len() {
h
} else {
sw
}
};
if truncated {
format!("{}...", strdata)
} else {
strdata
}
}
pub trait FormatterToString {
fn to_string<T: fmt::Display>(&self, value: T) -> String;
fn to_string_opt<T: fmt::Display>(&self, value: Option<T>) -> String {
if let Some(ts) = value {
self.to_string(ts)
} else {
"None".to_string()
}
}
fn to_multiline_string<'a, T, I>(&self, items: I) -> String
where
T: fmt::Display + 'a,
I: IntoIterator<Item = T>,
{
let mut out = Vec::new();
for x in items {
out.push(self.to_string(x));
}
out.join("\n")
}
fn to_multiline_indexed_string<'a, T, I>(&self, items: I) -> String
where
T: fmt::Display + 'a,
I: IntoIterator<Item = T>,
{
let mut out = Vec::new();
for (n, x) in items.into_iter().enumerate() {
let block = self.to_string(x);
if block.contains('\n') {
out.push(format!("{n}:"));
out.push(indent_all_by(DEFAULT_INDENT_SPACES, block));
} else {
out.push(format!("{n}: {block}"));
}
}
out.join("\n")
}
fn to_table_string<T: fmt::Display>(&self, items: &[T]) -> String {
self.to_table_string_custom(items, 32, 4, true, true)
}
fn to_table_string_custom<T: fmt::Display, I: IntoIterator<Item = T>>(
&self,
items: I,
columns: usize,
indent: usize,
brackets: bool,
newlines: bool,
) -> String {
let strings: Vec<String> = items.into_iter().map(|s| self.to_string(s)).collect();
table_string_from_strings(&strings, columns, indent, brackets, newlines)
}
}
fn table_string_from_strings(
strings: &[String],
columns: usize,
indent: usize,
brackets: bool,
newlines: bool,
) -> String {
let len = strings.len();
let mut col = 0usize;
let mut out = String::new();
let mut left = len;
let use_columns = columns > 0 && columns < len;
if brackets {
out.push('[');
}
if use_columns && newlines {
out.push('\n');
}
let indent_str = " ".repeat(indent);
for sc in strings {
if use_columns && col == 0 {
out.push_str(&indent_str);
}
out.push_str(sc);
col += 1;
left -= 1;
if left != 0 {
out.push(',');
if use_columns && col == columns {
col = 0;
out.push('\n');
}
}
}
if use_columns && newlines {
out.push('\n');
}
if brackets {
out.push(']');
}
out
}
impl FormatterToString for fmt::Formatter<'_> {
fn to_string<T: fmt::Display>(&self, value: T) -> String {
if self.alternate() {
format!("{:#}", value)
} else {
format!("{}", value)
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultDisplayFormat;
impl FormatterToString for DefaultDisplayFormat {
fn to_string<T: fmt::Display>(&self, value: T) -> String {
value.to_string()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AlternateDisplayFormat;
impl FormatterToString for AlternateDisplayFormat {
fn to_string<T: fmt::Display>(&self, value: T) -> String {
format!("{:#}", value)
}
}
pub trait ToMultilineString {
fn to_multiline_string(&self) -> String;
fn to_multiline_indexed_string(&self) -> String;
}
impl<T> ToMultilineString for Vec<T>
where
T: fmt::Display,
{
fn to_multiline_string(&self) -> String {
AlternateDisplayFormat.to_multiline_string(self.iter())
}
fn to_multiline_indexed_string(&self) -> String {
AlternateDisplayFormat.to_multiline_indexed_string(self.iter())
}
}
pub trait ToTableString {
fn to_table_string(&self) -> String {
self.to_table_string_custom(32, 4, true, true)
}
fn to_table_string_custom(
&self,
columns: usize,
indent: usize,
brackets: bool,
newlines: bool,
) -> String;
}
impl<T> ToTableString for Vec<T>
where
T: fmt::Display,
{
fn to_table_string_custom(
&self,
columns: usize,
indent: usize,
brackets: bool,
newlines: bool,
) -> String {
AlternateDisplayFormat.to_table_string_custom(self, columns, indent, brackets, newlines)
}
}
pub trait StringIfEmpty<X: ToString> {
fn string_if_empty(&self, empty_string: X) -> String;
}
impl<S: AsRef<str>, X: ToString> StringIfEmpty<X> for S {
fn string_if_empty(&self, empty_string: X) -> String {
let s = self.as_ref();
if s.is_empty() {
empty_string.to_string()
} else {
s.to_string()
}
}
}
pub fn split_port(name: &str) -> Result<(String, Option<u16>), String> {
if let Some(split) = name.rfind(':') {
let hoststr = &name[0..split];
let portstr = &name[split + 1..];
let port: u16 = portstr
.parse::<u16>()
.map_err(|e| format!("invalid port: {}", e))?;
Ok((hoststr.to_string(), Some(port)))
} else {
Ok((name.to_string(), None))
}
}
#[must_use]
pub fn prepend_slash(s: String) -> String {
if s.starts_with('/') {
return s;
}
let mut out = "/".to_owned();
out.push_str(s.as_str());
out
}
pub fn map_to_string<X: ToString>(arg: X) -> String {
arg.to_string()
}
#[cfg(test)]
mod formatter_to_string_tests {
use super::*;
#[derive(Clone, Copy)]
struct AltEcho(u32);
impl fmt::Display for AltEcho {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "[{}]", self.0)
} else {
write!(f, "{}", self.0)
}
}
}
struct Wrap<'a, T: fmt::Display>(&'a T);
impl<T: fmt::Display> fmt::Display for Wrap<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&f.to_string(self.0))
}
}
#[test]
fn nested_format_inherits_alternate() {
assert_eq!(format!("{}", Wrap(&AltEcho(3))), "3");
assert_eq!(format!("{:#}", Wrap(&AltEcho(3))), "[3]");
}
#[test]
fn default_table_string_matches_single_line_when_short() {
let v = [1u32, 2, 3];
assert_eq!(AlternateDisplayFormat.to_table_string(&v), "[1,2,3]");
}
#[test]
fn alternate_indexed_multiline_uses_hash_flag() {
let v = [AltEcho(7)];
let s = AlternateDisplayFormat.to_multiline_indexed_string(v.iter());
assert!(s.contains("[7]"), "got {:?}", s);
}
}