Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
yuryshulaev committed Feb 8, 2023
0 parents commit edbf5b6
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*]
indent_style = tab
indent_size = 4
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
/Cargo.lock
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "truncating-arraystring"
description = "ArrayString wrapper with truncating Write"
version = "0.1.0"
edition = "2021"
license = "MIT"
repository = "https://github.com/yuryshulaev/truncating-arraystring"
keywords = ["string", "buffer", "stack", "data-structure", "performance"]
categories = ["data-structures"]

[dependencies]
arrayvec = "0.7.2"
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# truncating-arraystring

[ArrayString](https://docs.rs/arrayvec/latest/arrayvec/struct.ArrayString.html) wrapper with truncating Write.

```rust
use std::fmt::Write;
use truncating_arraystring::TruncatingArrayString;

fn main() {
let mut buf = TruncatingArrayString::<5>::new();
assert_eq!(write!(buf, "{}", "12"), Ok(()));
assert_eq!(write!(buf, "{}", "3456789"), Err(std::fmt::Error));
assert_eq!(&buf.0[..], "12345");
}
```
83 changes: 83 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
pub use arrayvec::{ArrayString, CapacityError};
use std::fmt;

#[derive(Debug)]
pub struct TruncatingArrayString<const CAP: usize>(pub ArrayString<CAP>);

impl<const CAP: usize> TruncatingArrayString<CAP> {
pub fn new() -> Self {
Self(ArrayString::<CAP>::new())
}

pub fn try_push_str_truncate<'a>(&mut self, s: &'a str) -> Result<(), CapacityError<&'a str>> {
let remaining_capacity = self.0.capacity() - self.0.len();

if s.len() < remaining_capacity {
self.0.push_str(s);
Ok(())
} else {
let (fits, rest) = s.split_at(floor_char_boundary(s, remaining_capacity));
self.0.push_str(fits);
Err(CapacityError::new(rest))
}
}
}

impl<const CAP: usize> fmt::Display for TruncatingArrayString<CAP> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}

impl<const CAP: usize> fmt::Write for TruncatingArrayString<CAP> {
fn write_char(&mut self, c: char) -> fmt::Result {
self.0.try_push(c).map_err(|_| fmt::Error)
}

fn write_str(&mut self, s: &str) -> fmt::Result {
self.try_push_str_truncate(s).map_err(|_| fmt::Error)
}
}

// https://doc.rust-lang.org/std/primitive.str.html#method.floor_char_boundary
fn floor_char_boundary(s: &str, index: usize) -> usize {
if index >= s.len() {
s.len()
} else {
let lower_bound = index.saturating_sub(3);
let new_index = s.as_bytes()[lower_bound..=index]
.iter()
.rposition(|b| is_utf8_char_boundary(*b));

// SAFETY: we know that the character boundary will be within four bytes
unsafe { lower_bound + new_index.unwrap_unchecked() }
}
}

// https://doc.rust-lang.org/beta/src/core/num/mod.rs.html#883
const fn is_utf8_char_boundary(b: u8) -> bool {
// This is bit magic equivalent to: b < 128 || b >= 192
(b as i8) >= -0x40
}

#[cfg(test)]
mod tests {
use std::fmt::Write;
use super::*;

#[test]
fn it_truncates() {
let mut buf = TruncatingArrayString::<5>::new();
assert_eq!(write!(buf, "{}", "12"), Ok(()));
assert_eq!(write!(buf, "{}", "3456789"), Err(std::fmt::Error));
assert_eq!(&buf.0[..], "12345");
}

#[test]
fn it_truncates_at_char_boundary() {
let mut buf = TruncatingArrayString::<5>::new();
assert_eq!(write!(buf, "{}", "α"), Ok(()));
assert_eq!(write!(buf, "{}", "βγ"), Err(std::fmt::Error));
assert_eq!(&buf.0[..], "αβ");
}
}

0 comments on commit edbf5b6

Please sign in to comment.