1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Utf8Stats {
6 pub bytes: usize,
7 pub chars: usize,
8 pub is_ascii: bool,
9}
10
11#[must_use]
12pub fn is_valid_utf8(bytes: &[u8]) -> bool {
13 std::str::from_utf8(bytes).is_ok()
14}
15
16#[must_use]
17pub fn utf8_lossy(bytes: &[u8]) -> String {
18 String::from_utf8_lossy(bytes).into_owned()
19}
20
21#[must_use]
22pub fn utf8_stats(input: &str) -> Utf8Stats {
23 Utf8Stats {
24 bytes: input.len(),
25 chars: input.chars().count(),
26 is_ascii: input.is_ascii(),
27 }
28}
29
30#[must_use]
31pub fn byte_len(input: &str) -> usize {
32 input.len()
33}
34
35#[must_use]
36pub fn char_len(input: &str) -> usize {
37 input.chars().count()
38}
39
40#[must_use]
41pub fn truncate_utf8(input: &str, max_chars: usize) -> String {
42 input.chars().take(max_chars).collect()
43}
44
45#[must_use]
46pub fn truncate_utf8_bytes(input: &str, max_bytes: usize) -> String {
47 let boundary = safe_char_boundary(input, max_bytes);
48 input[..boundary].to_string()
49}
50
51#[must_use]
52pub fn safe_char_boundary(input: &str, index: usize) -> usize {
53 if index >= input.len() {
54 return input.len();
55 }
56
57 let mut boundary = index;
58 while boundary > 0 && !input.is_char_boundary(boundary) {
59 boundary -= 1;
60 }
61 boundary
62}