#![allow(clippy::trivially_copy_pass_by_ref)]
use std::fmt;
#[cfg(feature = "serde_json")]
mod json;
#[cfg(feature = "serde_json")]
pub use self::json::json;
#[cfg(feature = "serde_yaml")]
mod yaml;
#[cfg(feature = "serde_yaml")]
pub use self::yaml::yaml;
#[allow(unused_imports)]
use crate::error::Error::Fmt;
use askama_escape::{Escaper, MarkupDisplay};
#[cfg(feature = "humansize")]
use humansize::{file_size_opts, FileSize};
#[cfg(feature = "num-traits")]
use num_traits::{cast::NumCast, Signed};
#[cfg(feature = "percent-encoding")]
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use super::Result;
#[cfg(feature = "percent-encoding")]
const URLENCODE_STRICT_SET: &AsciiSet = &NON_ALPHANUMERIC
.remove(b'_')
.remove(b'.')
.remove(b'-')
.remove(b'~');
#[cfg(feature = "percent-encoding")]
const URLENCODE_SET: &AsciiSet = &URLENCODE_STRICT_SET.remove(b'/');
pub const BUILT_IN_FILTERS: &[&str] = &[
"abs",
"capitalize",
"center",
"e",
"escape",
"filesizeformat",
"fmt",
"format",
"indent",
"into_f64",
"into_isize",
"join",
"linebreaks",
"linebreaksbr",
"paragraphbreaks",
"lower",
"lowercase",
"safe",
"trim",
"truncate",
"upper",
"uppercase",
"urlencode",
"urlencode_strict",
"wordcount",
"json",
"markdown",
"yaml",
];
pub fn safe<E, T>(e: E, v: T) -> Result<MarkupDisplay<E, T>>
where
E: Escaper,
T: fmt::Display,
{
Ok(MarkupDisplay::new_safe(v, e))
}
pub fn escape<E, T>(e: E, v: T) -> Result<MarkupDisplay<E, T>>
where
E: Escaper,
T: fmt::Display,
{
Ok(MarkupDisplay::new_unsafe(v, e))
}
#[cfg(feature = "humansize")]
pub fn filesizeformat<B: FileSize>(b: &B) -> Result<String> {
b.file_size(file_size_opts::DECIMAL)
.map_err(|_| Fmt(fmt::Error))
}
#[cfg(feature = "percent-encoding")]
pub fn urlencode<T: fmt::Display>(s: T) -> Result<String> {
let s = s.to_string();
Ok(utf8_percent_encode(&s, URLENCODE_SET).to_string())
}
#[cfg(feature = "percent-encoding")]
pub fn urlencode_strict<T: fmt::Display>(s: T) -> Result<String> {
let s = s.to_string();
Ok(utf8_percent_encode(&s, URLENCODE_STRICT_SET).to_string())
}
pub fn fmt() {}
pub fn format() {}
pub fn linebreaks<T: fmt::Display>(s: T) -> Result<String> {
let s = s.to_string();
let linebroken = s.replace("\n\n", "</p><p>").replace('\n', "<br/>");
Ok(format!("<p>{}</p>", linebroken))
}
pub fn linebreaksbr<T: fmt::Display>(s: T) -> Result<String> {
let s = s.to_string();
Ok(s.replace('\n', "<br/>"))
}
pub fn paragraphbreaks<T: fmt::Display>(s: T) -> Result<String> {
let s = s.to_string();
let linebroken = s.replace("\n\n", "</p><p>").replace("<p></p>", "");
Ok(format!("<p>{}</p>", linebroken))
}
pub fn lower<T: fmt::Display>(s: T) -> Result<String> {
let s = s.to_string();
Ok(s.to_lowercase())
}
pub fn lowercase<T: fmt::Display>(s: T) -> Result<String> {
lower(s)
}
pub fn upper<T: fmt::Display>(s: T) -> Result<String> {
let s = s.to_string();
Ok(s.to_uppercase())
}
pub fn uppercase<T: fmt::Display>(s: T) -> Result<String> {
upper(s)
}
pub fn trim<T: fmt::Display>(s: T) -> Result<String> {
let s = s.to_string();
Ok(s.trim().to_owned())
}
pub fn truncate<T: fmt::Display>(s: T, len: usize) -> Result<String> {
let mut s = s.to_string();
if s.len() > len {
let mut real_len = len;
while !s.is_char_boundary(real_len) {
real_len += 1;
}
s.truncate(real_len);
s.push_str("...");
}
Ok(s)
}
pub fn indent<T: fmt::Display>(s: T, width: usize) -> Result<String> {
let s = s.to_string();
let mut indented = String::new();
for (i, c) in s.char_indices() {
indented.push(c);
if c == '\n' && i < s.len() - 1 {
for _ in 0..width {
indented.push(' ');
}
}
}
Ok(indented)
}
#[cfg(feature = "num-traits")]
pub fn into_f64<T>(number: T) -> Result<f64>
where
T: NumCast,
{
number.to_f64().ok_or(Fmt(fmt::Error))
}
#[cfg(feature = "num-traits")]
pub fn into_isize<T>(number: T) -> Result<isize>
where
T: NumCast,
{
number.to_isize().ok_or(Fmt(fmt::Error))
}
pub fn join<T, I, S>(input: I, separator: S) -> Result<String>
where
T: fmt::Display,
I: Iterator<Item = T>,
S: AsRef<str>,
{
let separator: &str = separator.as_ref();
let mut rv = String::new();
for (num, item) in input.enumerate() {
if num > 0 {
rv.push_str(separator);
}
rv.push_str(&format!("{}", item));
}
Ok(rv)
}
#[cfg(feature = "num-traits")]
pub fn abs<T>(number: T) -> Result<T>
where
T: Signed,
{
Ok(number.abs())
}
pub fn capitalize<T: fmt::Display>(s: T) -> Result<String> {
let mut s = s.to_string();
match s.get_mut(0..1).map(|s| {
s.make_ascii_uppercase();
&*s
}) {
None => Ok(s),
_ => {
s.get_mut(1..).map(|s| {
s.make_ascii_lowercase();
&*s
});
Ok(s)
}
}
}
pub fn center(src: &dyn fmt::Display, dst_len: usize) -> Result<String> {
let src = src.to_string();
let len = src.len();
if dst_len <= len {
Ok(src)
} else {
let diff = dst_len - len;
let mid = diff / 2;
let r = diff % 2;
let mut buf = String::with_capacity(dst_len);
for _ in 0..mid {
buf.push(' ');
}
buf.push_str(&src);
for _ in 0..mid + r {
buf.push(' ');
}
Ok(buf)
}
}
pub fn wordcount<T: fmt::Display>(s: T) -> Result<usize> {
let s = s.to_string();
Ok(s.split_whitespace().count())
}
#[cfg(feature = "markdown")]
pub fn markdown<E, S>(
e: E,
s: S,
options: Option<&comrak::ComrakOptions>,
) -> Result<MarkupDisplay<E, String>>
where
E: Escaper,
S: AsRef<str>,
{
use comrak::{
markdown_to_html, ComrakExtensionOptions, ComrakOptions, ComrakParseOptions,
ComrakRenderOptions,
};
const DEFAULT_OPTIONS: ComrakOptions = ComrakOptions {
extension: ComrakExtensionOptions {
strikethrough: true,
tagfilter: true,
table: true,
autolink: true,
tasklist: false,
superscript: false,
header_ids: None,
footnotes: false,
description_lists: false,
front_matter_delimiter: None,
},
parse: ComrakParseOptions {
smart: false,
default_info_string: None,
},
render: ComrakRenderOptions {
unsafe_: false,
escape: true,
hardbreaks: false,
github_pre_lang: false,
width: 0,
},
};
let s = markdown_to_html(s.as_ref(), options.unwrap_or(&DEFAULT_OPTIONS));
Ok(MarkupDisplay::new_safe(s, e))
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "num-traits")]
use std::f64::INFINITY;
#[cfg(feature = "humansize")]
#[test]
fn test_filesizeformat() {
assert_eq!(filesizeformat(&0).unwrap(), "0 B");
assert_eq!(filesizeformat(&999u64).unwrap(), "999 B");
assert_eq!(filesizeformat(&1000i32).unwrap(), "1 KB");
assert_eq!(filesizeformat(&1023).unwrap(), "1.02 KB");
assert_eq!(filesizeformat(&1024usize).unwrap(), "1.02 KB");
}
#[cfg(feature = "percent-encoding")]
#[test]
fn test_urlencoding() {
assert_eq!(urlencode(&"AZaz09").unwrap(), "AZaz09");
assert_eq!(urlencode_strict(&"AZaz09").unwrap(), "AZaz09");
assert_eq!(urlencode(&"_.-~").unwrap(), "_.-~");
assert_eq!(urlencode_strict(&"_.-~").unwrap(), "_.-~");
assert_eq!(urlencode(&":/?#[]@").unwrap(), "%3A/%3F%23%5B%5D%40");
assert_eq!(
urlencode_strict(&":/?#[]@").unwrap(),
"%3A%2F%3F%23%5B%5D%40"
);
assert_eq!(
urlencode(&"!$&'()*+,;=").unwrap(),
"%21%24%26%27%28%29%2A%2B%2C%3B%3D"
);
assert_eq!(
urlencode_strict(&"!$&'()*+,;=").unwrap(),
"%21%24%26%27%28%29%2A%2B%2C%3B%3D"
);
assert_eq!(
urlencode(&"žŠďŤňĚáÉóŮ").unwrap(),
"%C5%BE%C5%A0%C4%8F%C5%A4%C5%88%C4%9A%C3%A1%C3%89%C3%B3%C5%AE"
);
assert_eq!(
urlencode_strict(&"žŠďŤňĚáÉóŮ").unwrap(),
"%C5%BE%C5%A0%C4%8F%C5%A4%C5%88%C4%9A%C3%A1%C3%89%C3%B3%C5%AE"
);
assert_eq!(urlencode(&"🦀").unwrap(), "%F0%9F%A6%80");
assert_eq!(urlencode_strict(&"🦀").unwrap(), "%F0%9F%A6%80");
}
#[test]
fn test_linebreaks() {
assert_eq!(
linebreaks(&"Foo\nBar Baz").unwrap(),
"<p>Foo<br/>Bar Baz</p>"
);
assert_eq!(
linebreaks(&"Foo\nBar\n\nBaz").unwrap(),
"<p>Foo<br/>Bar</p><p>Baz</p>"
);
}
#[test]
fn test_linebreaksbr() {
assert_eq!(linebreaksbr(&"Foo\nBar").unwrap(), "Foo<br/>Bar");
assert_eq!(
linebreaksbr(&"Foo\nBar\n\nBaz").unwrap(),
"Foo<br/>Bar<br/><br/>Baz"
);
}
#[test]
fn test_paragraphbreaks() {
assert_eq!(
paragraphbreaks(&"Foo\nBar Baz").unwrap(),
"<p>Foo\nBar Baz</p>"
);
assert_eq!(
paragraphbreaks(&"Foo\nBar\n\nBaz").unwrap(),
"<p>Foo\nBar</p><p>Baz</p>"
);
assert_eq!(
paragraphbreaks(&"Foo\n\n\n\n\nBar\n\nBaz").unwrap(),
"<p>Foo</p><p>\nBar</p><p>Baz</p>"
);
}
#[test]
fn test_lower() {
assert_eq!(lower(&"Foo").unwrap(), "foo");
assert_eq!(lower(&"FOO").unwrap(), "foo");
assert_eq!(lower(&"FooBar").unwrap(), "foobar");
assert_eq!(lower(&"foo").unwrap(), "foo");
}
#[test]
fn test_upper() {
assert_eq!(upper(&"Foo").unwrap(), "FOO");
assert_eq!(upper(&"FOO").unwrap(), "FOO");
assert_eq!(upper(&"FooBar").unwrap(), "FOOBAR");
assert_eq!(upper(&"foo").unwrap(), "FOO");
}
#[test]
fn test_trim() {
assert_eq!(trim(&" Hello\tworld\t").unwrap(), "Hello\tworld");
}
#[test]
fn test_truncate() {
assert_eq!(truncate(&"hello", 2).unwrap(), "he...");
let a = String::from("您好");
assert_eq!(a.len(), 6);
assert_eq!(String::from("您").len(), 3);
assert_eq!(truncate(&"您好", 1).unwrap(), "您...");
assert_eq!(truncate(&"您好", 2).unwrap(), "您...");
assert_eq!(truncate(&"您好", 3).unwrap(), "您...");
assert_eq!(truncate(&"您好", 4).unwrap(), "您好...");
assert_eq!(truncate(&"您好", 6).unwrap(), "您好");
assert_eq!(truncate(&"您好", 7).unwrap(), "您好");
let s = String::from("🤚a🤚");
assert_eq!(s.len(), 9);
assert_eq!(String::from("🤚").len(), 4);
assert_eq!(truncate(&"🤚a🤚", 1).unwrap(), "🤚...");
assert_eq!(truncate(&"🤚a🤚", 2).unwrap(), "🤚...");
assert_eq!(truncate(&"🤚a🤚", 3).unwrap(), "🤚...");
assert_eq!(truncate(&"🤚a🤚", 4).unwrap(), "🤚...");
assert_eq!(truncate(&"🤚a🤚", 5).unwrap(), "🤚a...");
assert_eq!(truncate(&"🤚a🤚", 6).unwrap(), "🤚a🤚...");
assert_eq!(truncate(&"🤚a🤚", 9).unwrap(), "🤚a🤚");
assert_eq!(truncate(&"🤚a🤚", 10).unwrap(), "🤚a🤚");
}
#[test]
fn test_indent() {
assert_eq!(indent(&"hello", 2).unwrap(), "hello");
assert_eq!(indent(&"hello\n", 2).unwrap(), "hello\n");
assert_eq!(indent(&"hello\nfoo", 2).unwrap(), "hello\n foo");
assert_eq!(
indent(&"hello\nfoo\n bar", 4).unwrap(),
"hello\n foo\n bar"
);
}
#[cfg(feature = "num-traits")]
#[test]
#[allow(clippy::float_cmp)]
fn test_into_f64() {
assert_eq!(into_f64(1).unwrap(), 1.0_f64);
assert_eq!(into_f64(1.9).unwrap(), 1.9_f64);
assert_eq!(into_f64(-1.9).unwrap(), -1.9_f64);
assert_eq!(into_f64(INFINITY as f32).unwrap(), INFINITY);
assert_eq!(into_f64(-INFINITY as f32).unwrap(), -INFINITY);
}
#[cfg(feature = "num-traits")]
#[test]
fn test_into_isize() {
assert_eq!(into_isize(1).unwrap(), 1_isize);
assert_eq!(into_isize(1.9).unwrap(), 1_isize);
assert_eq!(into_isize(-1.9).unwrap(), -1_isize);
assert_eq!(into_isize(1.5_f64).unwrap(), 1_isize);
assert_eq!(into_isize(-1.5_f64).unwrap(), -1_isize);
match into_isize(INFINITY) {
Err(Fmt(fmt::Error)) => {}
_ => panic!("Should return error of type Err(Fmt(fmt::Error))"),
};
}
#[allow(clippy::needless_borrow)]
#[test]
fn test_join() {
assert_eq!(
join((&["hello", "world"]).iter(), ", ").unwrap(),
"hello, world"
);
assert_eq!(join((&["hello"]).iter(), ", ").unwrap(), "hello");
let empty: &[&str] = &[];
assert_eq!(join(empty.iter(), ", ").unwrap(), "");
let input: Vec<String> = vec!["foo".into(), "bar".into(), "bazz".into()];
assert_eq!(join(input.iter(), ":").unwrap(), "foo:bar:bazz");
let input: &[String] = &["foo".into(), "bar".into()];
assert_eq!(join(input.iter(), ":").unwrap(), "foo:bar");
let real: String = "blah".into();
let input: Vec<&str> = vec![&real];
assert_eq!(join(input.iter(), ";").unwrap(), "blah");
assert_eq!(
join((&&&&&["foo", "bar"]).iter(), ", ").unwrap(),
"foo, bar"
);
}
#[cfg(feature = "num-traits")]
#[test]
#[allow(clippy::float_cmp)]
fn test_abs() {
assert_eq!(abs(1).unwrap(), 1);
assert_eq!(abs(-1).unwrap(), 1);
assert_eq!(abs(1.0).unwrap(), 1.0);
assert_eq!(abs(-1.0).unwrap(), 1.0);
assert_eq!(abs(1.0_f64).unwrap(), 1.0_f64);
assert_eq!(abs(-1.0_f64).unwrap(), 1.0_f64);
}
#[test]
fn test_capitalize() {
assert_eq!(capitalize(&"foo").unwrap(), "Foo".to_string());
assert_eq!(capitalize(&"f").unwrap(), "F".to_string());
assert_eq!(capitalize(&"fO").unwrap(), "Fo".to_string());
assert_eq!(capitalize(&"").unwrap(), "".to_string());
assert_eq!(capitalize(&"FoO").unwrap(), "Foo".to_string());
assert_eq!(capitalize(&"foO BAR").unwrap(), "Foo bar".to_string());
}
#[test]
fn test_center() {
assert_eq!(center(&"f", 3).unwrap(), " f ".to_string());
assert_eq!(center(&"f", 4).unwrap(), " f ".to_string());
assert_eq!(center(&"foo", 1).unwrap(), "foo".to_string());
assert_eq!(center(&"foo bar", 8).unwrap(), "foo bar ".to_string());
}
#[test]
fn test_wordcount() {
assert_eq!(wordcount(&"").unwrap(), 0);
assert_eq!(wordcount(&" \n\t").unwrap(), 0);
assert_eq!(wordcount(&"foo").unwrap(), 1);
assert_eq!(wordcount(&"foo bar").unwrap(), 2);
}
}