use std::{str::CharIndices, sync::Arc};
#[cfg(feature = "file_memmap")]
use fs4::fs_std::FileExt;
#[cfg(feature = "file_memmap")]
use memmap2::Mmap;
#[cfg(feature = "file_memmap")]
use std::{fs::File, io};
#[derive(Debug, Clone)]
pub struct ImmutableString {
inner: Arc<ImmutableStringInner>,
}
impl ImmutableString {
#[inline]
fn from_inner(inner: ImmutableStringInner) -> Self {
ImmutableString {
inner: Arc::new(inner),
}
}
#[cfg(feature = "file_memmap")]
pub(super) fn new_locked_file(file: File, mem_map: Mmap) -> Self {
Self::from_inner(ImmutableStringInner::LockedFile {
locked_file: file,
mem_map,
})
}
pub(super) fn new_owned(boxed_str: Box<str>) -> Self {
Self::from_inner(ImmutableStringInner::Owned(boxed_str))
}
pub(super) fn new_static(str_ref: &'static str) -> Self {
Self::from_inner(ImmutableStringInner::Static(str_ref))
}
pub fn line_starts(&self) -> impl Iterator<Item = usize> + use<'_> {
let mut char_indices: CharIndices = self.as_ref().char_indices();
let mut last_was_newline: bool = true;
let iter = std::iter::from_fn(move || {
let (index, next) = char_indices.next()?;
let result = Some(last_was_newline.then_some(index));
last_was_newline = next == '\n';
result
});
iter.flatten()
}
pub fn as_str(&self) -> &str {
self.as_ref()
}
pub fn len(&self) -> usize {
self.as_str().len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl AsRef<str> for ImmutableString {
fn as_ref(&self) -> &str {
(*self.inner).as_ref()
}
}
#[derive(Debug)]
enum ImmutableStringInner {
Static(&'static str),
Owned(Box<str>),
#[cfg(feature = "file_memmap")]
LockedFile {
locked_file: File,
mem_map: Mmap,
},
}
#[cfg(feature = "file_memmap")]
impl Drop for ImmutableStringInner {
fn drop(&mut self) {
match self {
ImmutableStringInner::LockedFile { locked_file, .. } => {
FileExt::unlock(locked_file)
.map_err(|io_err: io::Error| eprintln!("{}", io_err))
.ok();
}
ImmutableStringInner::Owned(_) | ImmutableStringInner::Static(_) => {}
}
}
}
impl AsRef<str> for ImmutableStringInner {
fn as_ref(&self) -> &str {
match self {
ImmutableStringInner::Static(str) => str,
ImmutableStringInner::Owned(str) => str,
#[cfg(feature = "file_memmap")]
ImmutableStringInner::LockedFile { mem_map, .. } => {
let raw_data: &[u8] = mem_map.as_ref();
unsafe { std::str::from_utf8_unchecked(raw_data) }
}
}
}
}
#[cfg(test)]
mod tests {
use super::ImmutableString;
#[test]
fn test_line_starts() {
let v: Vec<usize> = ImmutableString::new_static("a\n\nb\nc")
.line_starts()
.collect();
assert_eq!(v.as_slice(), &[0, 2, 3, 5]);
}
}