1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#[cfg(feature = "ffi")]
pub use ::std::ffi::{CStr, CString};
pub use ::std::os::raw::c_char;
/// Extension for &str and &String.
pub trait StrXlf {
/// Convert the string to CString
#[cfg(feature = "ffi")]
fn to_cstring(&self) -> CString;
/// Copy string to a C-str slice.
///
/// Like GNU
///
/// ```C++
/// size_t strlcpy(char *dst, const char *src, size_t size);
/// ```
///
/// Take the full size of the buffer (not just the length)
/// and guarantee to NUL-terminate destination (as long as size is larger than 0).
#[cfg(feature = "ffi")]
fn strlcpy(&self, dst: &mut [u8]);
/// Format a string by the template.
#[cfg(feature = "regex")]
fn render<F>(&self, f: F) -> std::borrow::Cow<'_, str>
where
F: Fn(&str, &mut String);
}
pub trait BytesXlf {
/// Try to convert a nul-terminated bytes in UTF-8 to a string slice.
///
/// Allow the nul byte to be in any position of the string.
///
/// If the string does not contain a nul byte, the entire string is returned.
///
/// # Examples
///
/// ```
/// use xelf::str::BytesXlf;
///
/// assert_eq!(b"a\0bc".to_utf8_with_nul(), Ok("a"));
/// assert_eq!(b"abc".to_utf8_with_nul(), Ok("abc"));
/// ```
fn to_utf8_with_nul(&self) -> Result<&str, std::str::Utf8Error>;
/// Try to convert a nul-terminated bytes in UTF-8 to a string.
///
/// Allow the nul byte at position of the string.
///
/// If the string does not contain a nul byte, the entire string is returned.
///
/// # Examples
///
/// ```
/// use xelf::str::BytesXlf;
///
/// assert_eq!(b"a\0bc".to_utf8_string_with_nul(), Ok("a".to_owned()));
/// assert_eq!(b"abc".to_utf8_string_with_nul(), Ok("abc".to_owned()));
/// ```
fn to_utf8_string_with_nul(&self) -> Result<String, std::str::Utf8Error>;
/// Convert a nul-terminated bytes in UTF-8 to a string.
///
/// Allow the nul byte at any position of the string.
///
/// If the string does not contain a nul byte, the entire string is returned.
///
/// # Examples
///
/// ```
/// use xelf::str::BytesXlf;
///
/// assert_eq!(b"a\0bc".to_utf8_lossy_with_nul(), "a");
/// assert_eq!(b"abc".to_utf8_lossy_with_nul(), "abc");
/// ```
fn to_utf8_lossy_with_nul(&self) -> std::borrow::Cow<'_, str>;
/// Try to convert a nul-terminated bytes in UTF-8 to a CStr slice.
///
/// Allow the nul byte at any position of the string.
///
/// If the string does not contain a nul byte, return `None`.
///
/// # Examples
///
/// ```
/// use xelf::str::BytesXlf;
/// use std::ffi::CStr;
///
/// assert_eq!(b"a\0bc".to_cstr_with_nul(), Some(CStr::from_bytes_with_nul(b"a\0").unwrap()));
/// assert_eq!(b"abc".to_cstr_with_nul(), None);
/// ```
#[cfg(feature = "ffi")]
fn to_cstr_with_nul(&self) -> Option<&CStr>;
}
impl<T: AsRef<str>> StrXlf for T {
#[cfg(feature = "ffi")]
#[inline]
fn to_cstring(&self) -> CString {
CString::new(self.as_ref()).unwrap()
}
#[cfg(feature = "ffi")]
fn strlcpy(&self, dst: &mut [u8]) {
if !dst.is_empty() {
let mut len = 0;
for (a, b) in dst.iter_mut().zip(self.as_ref().bytes()) {
*a = b;
if b == 0 {
return;
}
len += 1;
}
dst[std::cmp::min(len, dst.len() - 1)] = 0;
}
}
/// Render a string by the template with named arguments.
///
/// A name of an argument is wrapped by {}, like "{name}".
///
/// "{{" will be converted into "{", and "}}" well be converted into "}".
///
/// # Arguments
///
/// * f: function to render.
///
/// # Exmaples
///
/// ```
/// use xelf::str::StrXlf;
///
/// assert_eq!("{{".render(|_, _| ()), "{");
/// assert_eq!("}}".render(|_, _| ()), "}");
/// assert_eq!(
/// "{:a}-{b}".render(|key, dst| {
/// match key {
/// ":a" => dst.push_str("111"),
/// "b" => dst.push_str("222"),
/// _ => unimplemented!(),
/// }
/// }),
/// "111-222"
/// );
/// ```
///
#[cfg(feature = "regex")]
fn render<F>(&self, f: F) -> std::borrow::Cow<'_, str>
where
F: Fn(&str, &mut String),
{
use once_cell::sync::Lazy;
use regex::{Regex, Replacer};
struct _Replacer<F: Fn(&str, &mut String)>(F);
impl<F: Fn(&str, &mut String)> Replacer for _Replacer<F> {
fn replace_append(&mut self, caps: ®ex::Captures<'_>, dst: &mut String) {
let name = caps.get(0).unwrap().as_str();
match name {
"{{" => dst.push('{'),
"}}" => dst.push('}'),
_ => self.0(&name[1..name.len() - 1], dst),
}
}
}
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?:\{\{|\}\}|\{:?[[:word:]]+\})").unwrap());
let replacer = _Replacer(f);
RE.replace_all(self.as_ref(), replacer)
}
}
impl<T: AsRef<[u8]>> BytesXlf for T {
fn to_utf8_with_nul(&self) -> Result<&str, std::str::Utf8Error> {
let data = self.as_ref();
// default to length if no `\0` present
let nul_range_end = data.iter().position(|&c| c == 0).unwrap_or(data.len());
std::str::from_utf8(&data[..nul_range_end])
}
fn to_utf8_string_with_nul(&self) -> Result<String, std::str::Utf8Error> {
self.to_utf8_with_nul().map(|s| s.to_owned())
}
fn to_utf8_lossy_with_nul(&self) -> std::borrow::Cow<'_, str> {
let data = self.as_ref();
// default to length if no `\0` present
let nul_range_end = data.iter().position(|&c| c == 0).unwrap_or(data.len());
String::from_utf8_lossy(&data[..nul_range_end])
}
#[cfg(feature = "ffi")]
fn to_cstr_with_nul(&self) -> Option<&CStr> {
let data = self.as_ref();
data.iter()
.position(|&c| c == 0)
.and_then(|x| CStr::from_bytes_with_nul(&data[..=x]).ok())
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[cfg(feature = "regex")]
#[test]
fn test_template_render() {
assert_eq!("{{".render(|_, _| ()), "{");
assert_eq!("}}".render(|_, _| ()), "}");
assert_eq!(
"{a}-{b}".render(|key, dst| {
match key {
"a" => dst.push_str("111"),
"b" => dst.push_str("222"),
_ => unimplemented!(),
}
}),
"111-222"
);
assert_eq!(
"{{{:a_a}}}{bb}{{cc}}".render(|key, dst| {
match key {
":a_a" => dst.push_str("AAAA"),
"bb" => dst.push_str("BBBB"),
_ => unimplemented!(),
}
}),
"{AAAA}BBBB{cc}"
);
}
}