use crate::output::diagnostic;
pub fn is_identifier(s: &str) -> bool {
static IDENTIFIER_RE: once_cell::sync::Lazy<regex::Regex> =
once_cell::sync::Lazy::new(|| regex::Regex::new("[a-zA-Z_$][a-zA-Z0-9_$!:\\.]*").unwrap());
IDENTIFIER_RE.is_match(s)
}
pub fn check_uri(s: &str) -> diagnostic::Result<uriparse::URIReference> {
uriparse::URIReference::try_from(s).map_err(|e| ecause!(IllegalUri, e))
}
pub fn check_uri_glob(s: &str) -> diagnostic::Result<()> {
let uri = check_uri(s)?;
let path = uri.path().to_string();
let decoded_path = percent_encoding::percent_decode_str(&path).decode_utf8_lossy();
glob::Pattern::new(&decoded_path).map_err(|e| ecause!(IllegalGlob, e))?;
Ok(())
}
pub fn as_quoted_string<S: AsRef<str>>(s: S) -> String {
let s = s.as_ref();
let mut result = String::with_capacity(s.len() + 2);
result.push('"');
for c in s.chars() {
match c {
'\\' => result += "\\\\",
'"' => result += "\"",
c => result.push(c),
}
}
result.push('"');
result
}
pub fn as_ident_or_string<S: AsRef<str>>(s: S) -> String {
let s = s.as_ref();
if is_identifier(s) {
s.to_string()
} else {
as_quoted_string(s)
}
}
pub fn describe_nth(index: u32) -> String {
match index {
0 => String::from("zeroth"),
1 => String::from("first"),
2 => String::from("second"),
3 => String::from("third"),
4 => String::from("fourth"),
5 => String::from("fifth"),
6 => String::from("sixth"),
7 => String::from("seventh"),
8 => String::from("eighth"),
9 => String::from("ninth"),
10 => String::from("tenth"),
11 => String::from("eleventh"),
12 => String::from("twelfth"),
13 => String::from("thirteenth"),
14 => String::from("fourteenth"),
15 => String::from("fifteenth"),
16 => String::from("sixteenth"),
17 => String::from("seventeenth"),
18 => String::from("eighteenth"),
19 => String::from("nineteenth"),
20 => String::from("twentieth"),
_ => match index % 10 {
1 => format!("{index}st"),
2 => format!("{index}nd"),
3 => format!("{index}rd"),
_ => format!("{index}th"),
},
}
}
pub fn describe_index(index: i32) -> String {
match index {
i32::MIN..=-2 => format!("the {} to last", describe_nth(-index as u32)),
-1 => String::from("the last"),
0..=i32::MAX => format!("the {}", describe_nth((index + 1) as u32)),
}
}
#[derive(Clone, Copy, Debug)]
pub struct Limit {
limit: Option<usize>,
}
impl Default for Limit {
fn default() -> Self {
Self { limit: Some(100) }
}
}
impl Limit {
pub fn new(limit: usize) -> Self {
Self { limit: Some(limit) }
}
pub fn unlimited() -> Self {
Self { limit: None }
}
pub fn chars(&self) -> usize {
self.limit.unwrap_or(usize::MAX)
}
pub fn split(self, min_amount: usize) -> (Self, Self) {
if let Some(limit) = self.limit {
if limit < min_amount {
(Self::new(limit), Self::new(0))
} else {
(Self::new(min_amount), Self::new(limit - min_amount))
}
} else {
(Self::unlimited(), Self::unlimited())
}
}
pub fn split_n(
self,
num_elements: usize,
min_element_size: usize,
) -> (usize, Option<usize>, Limit) {
if let Some(limit) = self.limit {
let n = limit.checked_div(min_element_size).unwrap_or(usize::MAX);
if n < num_elements {
let n_right = (n + 1) / 3;
let n_left = n - n_right;
let limit = Self::new(limit.checked_div(n).unwrap_or(limit));
(n_left, Some(n_right), limit)
} else {
(
num_elements,
None,
Self::new(limit.checked_div(num_elements).unwrap_or(limit)),
)
}
} else {
(num_elements, None, Self::unlimited())
}
}
pub fn split_ns(self, elements: &[usize]) -> (usize, Option<usize>) {
if let Some(limit) = self.limit {
if elements.iter().cloned().sum::<usize>() > limit {
let mut remain = (limit + 1) / 3;
let mut total = 0;
let mut n_right = 0;
for size in elements.iter().rev() {
let size = *size;
if size > remain {
n_right += 1;
remain -= size;
total += size;
} else {
break;
}
}
let mut remain = limit - total;
let mut n_left = 0;
for size in elements.iter() {
let size = *size;
if size > remain {
n_left += 1;
remain -= size;
} else {
break;
}
}
return (n_left, Some(n_right));
}
}
(elements.len(), None)
}
}
pub trait Describe {
fn describe(&self, f: &mut std::fmt::Formatter<'_>, limit: Limit) -> std::fmt::Result;
fn display(&self) -> Describer<Self> {
Describer(self)
}
}
pub struct Describer<'a, T: Describe + ?Sized>(&'a T);
impl<T: Describe> std::fmt::Display for Describer<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.describe(
f,
if f.alternate() {
Limit::unlimited()
} else {
Limit::default()
},
)
}
}
pub fn describe_identifier(
f: &mut std::fmt::Formatter<'_>,
data: &str,
limit: Limit,
) -> std::fmt::Result {
if is_identifier(data) {
return write_truncated_str(f, data, limit, false);
}
describe_string(f, data, limit)
}
pub fn describe_string(
f: &mut std::fmt::Formatter<'_>,
data: &str,
limit: Limit,
) -> std::fmt::Result {
write_truncated_str(f, data, limit, true)
}
fn write_truncated_str(
f: &mut std::fmt::Formatter<'_>,
data: &str,
limit: Limit,
quote: bool,
) -> std::fmt::Result {
let chars: Vec<char> = data.chars().collect();
let char_count = chars.len();
let (n_left, n_right, _) = limit.split_n(char_count, 1);
if n_left > 0 || n_right.is_none() {
let left_part: String = chars.iter().take(n_left).collect();
if quote {
write!(f, "{}", as_quoted_string(&left_part))?;
} else {
write!(f, "{}", left_part)?;
}
}
if let Some(n_right) = n_right {
write!(f, "..")?;
if n_right > 0 {
let right_part: String = chars.iter().skip(char_count - n_right).collect();
if quote {
write!(f, "{}", as_quoted_string(&right_part))?;
} else {
write!(f, "{}", right_part)?;
}
}
}
Ok(())
}
fn describe_binary_all(f: &mut std::fmt::Formatter<'_>, data: &[u8]) -> std::fmt::Result {
let mut first = true;
for byte in data {
if first {
first = false;
} else {
write!(f, " ")?;
}
write!(f, "{byte:02X}")?;
}
Ok(())
}
pub fn describe_binary(
f: &mut std::fmt::Formatter<'_>,
data: &[u8],
limit: Limit,
) -> std::fmt::Result {
let (n_left, n_right, _) = limit.split_n(data.len(), 3);
describe_binary_all(f, &data[..n_left])?;
if let Some(n_right) = n_right {
write!(f, "..")?;
describe_binary_all(f, &data[data.len() - n_right..])?;
}
Ok(())
}
fn describe_sequence_all<T, F>(
f: &mut std::fmt::Formatter<'_>,
values: &[T],
offset: usize,
el_limit: Limit,
repr: &F,
) -> std::fmt::Result
where
F: Fn(&mut std::fmt::Formatter<'_>, &T, usize, Limit) -> std::fmt::Result,
{
let mut first = true;
for (index, value) in values.iter().enumerate() {
if first {
first = false;
} else {
write!(f, ", ")?;
}
repr(f, value, index + offset, el_limit)?;
}
Ok(())
}
pub fn describe_sequence<T, F>(
f: &mut std::fmt::Formatter<'_>,
values: &[T],
limit: Limit,
element_size: usize,
repr: F,
) -> std::fmt::Result
where
F: Fn(&mut std::fmt::Formatter<'_>, &T, usize, Limit) -> std::fmt::Result,
{
describe_sequence_with_sep(f, values, limit, element_size, ',', repr)
}
pub fn describe_sequence_with_sep<T, F>(
f: &mut std::fmt::Formatter<'_>,
values: &[T],
limit: Limit,
element_size: usize,
separator: char,
repr: F,
) -> std::fmt::Result
where
F: Fn(&mut std::fmt::Formatter<'_>, &T, usize, Limit) -> std::fmt::Result,
{
let (n_left, n_right, el_limit) = limit.split_n(values.len(), element_size);
describe_sequence_all(f, &values[..n_left], 0, el_limit, &repr)?;
if let Some(n_right) = n_right {
if n_left > 0 {
write!(f, "{separator} ")?;
}
write!(f, "..")?;
if n_right > 0 {
write!(f, "{separator} ")?;
}
let offset = values.len() - n_right;
describe_sequence_all(f, &values[offset..], offset, el_limit, &repr)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn test_describe_with_limit<F>(input: &str, limit: Limit, describe_fn: F) -> String
where
F: Fn(&mut std::fmt::Formatter<'_>, &str, Limit) -> std::fmt::Result,
{
struct TestWrapper<'a, F> {
input: &'a str,
limit: Limit,
describe_fn: F,
}
impl<F> std::fmt::Display for TestWrapper<'_, F>
where
F: Fn(&mut std::fmt::Formatter<'_>, &str, Limit) -> std::fmt::Result,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(self.describe_fn)(f, self.input, self.limit)
}
}
format!(
"{}",
TestWrapper {
input,
limit,
describe_fn
}
)
}
#[test]
fn test_describe_identifier() {
let test_cases = [
("Hello World!", r#"He..!"#),
("ID测试数据测试数据", r#"ID..据"#),
("Hello World! 😀", r#"He..😀"#),
];
for (input, expected) in test_cases {
let output = test_describe_with_limit(input, Limit::new(3), describe_identifier);
assert_eq!(output, expected, "Failed for input: {}", input);
}
}
#[test]
fn test_describe_string() {
let test_cases = [
("Hello World!", r#""He".."!""#),
("测试数据测试数据", r#""测试".."据""#),
("Hello World! 😀", r#""He".."😀""#),
];
for (input, expected) in test_cases {
let output = test_describe_with_limit(input, Limit::new(3), describe_string);
assert_eq!(output, expected, "Failed for input: {}", input);
}
}
}