1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct EnsError {
5 message: String,
6}
7
8impl EnsError {
9 pub fn new(message: impl Into<String>) -> Self {
10 Self {
11 message: message.into(),
12 }
13 }
14
15 pub fn message(&self) -> &str {
16 &self.message
17 }
18}
19
20impl fmt::Display for EnsError {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 f.write_str(&self.message)
23 }
24}
25
26impl std::error::Error for EnsError {}
27
28pub type Result<T> = std::result::Result<T, EnsError>;
29
30pub fn hex_cp(cp: u32) -> String {
31 let mut s = format!("{cp:X}");
32 if s.len() < 2 {
33 s.insert(0, '0');
34 }
35 s
36}
37
38pub fn quote_cp(cp: u32) -> String {
39 format!("{{{}}}", hex_cp(cp))
40}
41
42pub fn explode_cp(s: &str) -> Vec<u32> {
43 s.chars().map(|c| c as u32).collect()
44}
45
46pub fn str_from_cps(cps: &[u32]) -> Result<String> {
47 let mut s = String::new();
48 for &cp in cps {
49 let ch = char::from_u32(cp)
50 .ok_or_else(|| EnsError::new(format!("invalid code point: {}", quote_cp(cp))))?;
51 s.push(ch);
52 }
53 Ok(s)
54}
55
56pub(crate) fn compare_arrays(a: &[u32], b: &[u32]) -> i32 {
57 let mut c = a.len() as i32 - b.len() as i32;
58 let mut i = 0;
59 while c == 0 && i < a.len() {
60 c = a[i] as i32 - b[i] as i32;
61 i += 1;
62 }
63 c
64}
65
66pub(crate) fn array_replace(v: &mut [u32], a: u32, b: u32) {
67 for cp in v {
68 if *cp == a {
69 *cp = b;
70 }
71 }
72}
73
74pub(crate) fn bidi_qq(s: &str) -> String {
75 format!("\"{s}\"\u{200E}")
76}
77
78pub fn safe_str_from_cps(cps: &[u32], max: Option<usize>) -> String {
79 safe_str_from_cps_with(cps, max, "e_cp)
80}
81
82pub(crate) fn safe_str_from_cps_with(
83 cps: &[u32],
84 max: Option<usize>,
85 quoter: &dyn Fn(u32) -> String,
86) -> String {
87 use crate::spec::{is_combining_mark, should_escape};
88
89 let max = max.unwrap_or(usize::MAX);
90 let mut working: Vec<u32> = if cps.len() > max {
91 let half = max >> 1;
92 let mut v = Vec::with_capacity(half * 2 + 1);
93 v.extend_from_slice(&cps[..half]);
94 v.push(0x2026);
95 v.extend_from_slice(&cps[cps.len().saturating_sub(half)..]);
96 v
97 } else {
98 cps.to_vec()
99 };
100
101 let mut buf = String::new();
102 if working
103 .first()
104 .copied()
105 .is_some_and(|cp| is_combining_mark(cp, false))
106 {
107 buf.push('\u{25CC}');
108 }
109
110 let mut prev = 0;
111 for i in 0..working.len() {
112 let cp = working[i];
113 if should_escape(cp) {
114 if let Ok(s) = str_from_cps(&working[prev..i]) {
115 buf.push_str(&s);
116 }
117 buf.push_str("er(cp));
118 prev = i + 1;
119 }
120 }
121 if let Ok(s) = str_from_cps(&working[prev..]) {
122 buf.push_str(&s);
123 }
124 working.clear();
125 buf
126}