use core::{
borrow::Borrow,
cmp::Ordering,
convert::Infallible,
ffi::{CStr, c_char},
fmt::{self, Write},
hash,
mem::ManuallyDrop,
ops::{Add, AddAssign},
ptr::{self, NonNull},
};
#[cfg(feature = "alloc")]
use alloc::{borrow::Cow, boxed::Box, ffi::CString};
use flipperzero_sys as sys;
mod iter;
use self::iter::{Bytes, CharIndices, Chars};
mod pattern;
use self::pattern::Pattern;
const WHITESPACE: &[char] = &[
' ', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\u{0085}', '\u{00A0}', '\u{1680}', '\u{2000}',
'\u{2001}', '\u{2002}', '\u{2003}', '\u{2004}', '\u{2005}', '\u{2006}', '\u{2007}', '\u{2008}',
'\u{2009}', '\u{200A}', '\u{2028}', '\u{2029}', '\u{202F}', '\u{205F}', '\u{3000}',
];
#[derive(Eq)]
pub struct FuriString(NonNull<sys::FuriString>);
impl Drop for FuriString {
fn drop(&mut self) {
unsafe { sys::furi_string_free(self.0.as_ptr()) };
}
}
impl FuriString {
#[inline]
#[must_use]
pub fn new() -> Self {
FuriString(unsafe { NonNull::new_unchecked(sys::furi_string_alloc()) })
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
let s = Self::new();
unsafe { sys::furi_string_reserve(s.0.as_ptr(), capacity + 1) };
s
}
#[inline]
#[must_use]
pub fn into_raw(self) -> NonNull<sys::FuriString> {
let s = ManuallyDrop::new(self);
s.0
}
#[inline]
#[must_use]
pub fn as_c_ptr(&self) -> *const c_char {
let ptr = self.0.as_ptr();
unsafe { sys::furi_string_get_cstr(ptr) }
}
#[inline]
#[must_use]
pub fn as_c_str(&self) -> &CStr {
let c_ptr = self.as_c_ptr();
unsafe { CStr::from_ptr(c_ptr) }
}
#[inline]
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut sys::FuriString {
self.0.as_ptr()
}
#[inline]
pub fn push_string(&mut self, string: &FuriString) {
unsafe { sys::furi_string_cat(self.0.as_ptr(), string.0.as_ptr()) }
}
#[inline]
pub fn push_str(&mut self, string: &str) {
self.extend(string.chars());
}
#[inline]
pub fn push_c_str(&mut self, string: &CStr) {
unsafe { sys::furi_string_cat_str(self.0.as_ptr(), string.as_ptr()) }
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
unsafe { sys::furi_string_reserve(self.0.as_ptr(), self.len() + additional + 1) };
}
#[inline]
pub fn push(&mut self, ch: char) {
match ch.len_utf8() {
1 => unsafe { sys::furi_string_push_back(self.0.as_ptr(), ch as c_char) },
_ => unsafe {
sys::furi_string_utf8_push(self.0.as_ptr(), ch as sys::FuriStringUnicodeValue)
},
}
}
#[inline]
#[must_use]
pub fn to_bytes(&self) -> &[u8] {
self.as_c_str().to_bytes()
}
#[inline]
#[must_use]
pub fn to_bytes_with_nul(&self) -> &[u8] {
self.as_c_str().to_bytes_with_nul()
}
#[inline]
pub fn truncate(&mut self, new_len: usize) {
unsafe { sys::furi_string_left(self.0.as_ptr(), new_len) };
}
#[inline]
pub fn insert(&mut self, idx: usize, ch: char) {
let mut buf = [0; 4];
let encoded = ch.encode_utf8(&mut buf).as_bytes();
self.insert_bytes(idx, encoded);
}
#[inline]
pub fn insert_str(&mut self, idx: usize, string: &str) {
self.insert_bytes(idx, string.as_bytes());
}
fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) {
let len = self.len();
assert!(idx <= len);
let amt = bytes.len();
self.reserve(amt);
for byte in bytes.iter() {
unsafe { sys::furi_string_push_back(self.0.as_ptr(), *byte as c_char) };
}
let c_ptr = self.as_c_ptr().cast::<u8>();
unsafe {
ptr::copy(c_ptr.add(idx), c_ptr.cast_mut().add(idx + amt), len - idx);
ptr::copy_nonoverlapping(bytes.as_ptr(), c_ptr.cast_mut().add(idx), amt);
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
unsafe { sys::furi_string_size(self.0.as_ptr()) }
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
unsafe { sys::furi_string_empty(self.0.as_ptr()) }
}
#[inline]
#[must_use = "use `.truncate()` if you don't need the other half"]
pub fn split_off(&mut self, at: usize) -> FuriString {
assert!(at <= self.len());
let ret = FuriString(unsafe {
NonNull::new_unchecked(sys::furi_string_alloc_set_str(self.as_c_ptr().add(at)))
});
self.truncate(at);
ret
}
#[inline]
pub fn clear(&mut self) {
unsafe { sys::furi_string_reset(self.0.as_ptr()) };
}
}
impl FuriString {
#[inline]
pub fn chars_lossy(&self) -> Chars<'_> {
Chars {
iter: self.to_bytes().iter(),
}
}
#[inline]
pub fn char_indices_lossy(&self) -> CharIndices<'_> {
CharIndices {
front_offset: 0,
iter: self.chars_lossy(),
}
}
#[inline]
pub fn bytes(&self) -> Bytes<'_> {
Bytes(self.to_bytes().iter().copied())
}
#[inline]
pub fn contains<P: Pattern>(&self, pat: P) -> bool {
pat.is_contained_in(self)
}
pub fn starts_with<P: Pattern>(&self, pat: P) -> bool {
pat.is_prefix_of(self)
}
pub fn ends_with<P: Pattern>(&self, pat: P) -> bool {
pat.is_suffix_of(self)
}
#[inline]
pub fn find<P: Pattern>(&self, pat: P) -> Option<usize> {
pat.find_in(self)
}
#[inline]
pub fn rfind<P: Pattern>(&self, pat: P) -> Option<usize> {
pat.rfind_in(self)
}
#[inline]
pub fn trim(&mut self) {
self.trim_matches(WHITESPACE)
}
#[inline]
pub fn trim_start(&mut self) {
self.trim_start_matches(WHITESPACE)
}
#[inline]
pub fn trim_end(&mut self) {
self.trim_end_matches(WHITESPACE)
}
pub fn trim_matches<P: Pattern + Copy>(&mut self, pat: P) {
self.trim_start_matches(pat);
self.trim_end_matches(pat);
}
pub fn trim_start_matches<P: Pattern + Copy>(&mut self, pat: P) {
while self.strip_prefix(pat) {}
}
pub fn trim_end_matches<P: Pattern + Copy>(&mut self, pat: P) {
while self.strip_suffix(pat) {}
}
#[must_use]
pub fn strip_prefix<P: Pattern>(&mut self, prefix: P) -> bool {
prefix.strip_prefix_of(self)
}
#[must_use]
pub fn strip_suffix<P: Pattern>(&mut self, suffix: P) -> bool {
suffix.strip_suffix_of(self)
}
}
impl Clone for FuriString {
fn clone(&self) -> Self {
Self(unsafe { NonNull::new_unchecked(sys::furi_string_alloc_set(self.0.as_ptr())) })
}
}
impl Default for FuriString {
fn default() -> Self {
Self::new()
}
}
impl AsRef<CStr> for FuriString {
#[inline]
fn as_ref(&self) -> &CStr {
self.as_c_str()
}
}
impl Borrow<CStr> for FuriString {
fn borrow(&self) -> &CStr {
self.as_c_str()
}
}
impl From<char> for FuriString {
fn from(value: char) -> Self {
let mut buf = FuriString::with_capacity(value.len_utf8());
buf.push(value);
buf
}
}
impl From<&str> for FuriString {
fn from(value: &str) -> Self {
let mut buf = FuriString::with_capacity(value.len());
buf.push_str(value);
buf
}
}
impl From<&mut str> for FuriString {
fn from(value: &mut str) -> Self {
From::<&str>::from(value)
}
}
impl From<&CStr> for FuriString {
fn from(value: &CStr) -> Self {
Self(unsafe { NonNull::new_unchecked(sys::furi_string_alloc_set_str(value.as_ptr())) })
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<Box<str>> for FuriString {
fn from(value: Box<str>) -> Self {
FuriString::from(value.as_ref())
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> From<Cow<'a, str>> for FuriString {
fn from(value: Cow<'a, str>) -> Self {
FuriString::from(value.as_ref())
}
}
impl Extend<FuriString> for FuriString {
fn extend<T: IntoIterator<Item = FuriString>>(&mut self, iter: T) {
iter.into_iter().for_each(move |s| self.push_string(&s));
}
}
impl Extend<char> for FuriString {
fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
let iterator = iter.into_iter();
let (lower_bound, _) = iterator.size_hint();
self.reserve(lower_bound);
iterator.for_each(move |c| self.push(c));
}
}
impl<'a> Extend<&'a char> for FuriString {
fn extend<T: IntoIterator<Item = &'a char>>(&mut self, iter: T) {
self.extend(iter.into_iter().cloned());
}
}
impl<'a> Extend<&'a str> for FuriString {
fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
iter.into_iter().for_each(move |s| self.push_str(s));
}
}
impl<'a> Extend<&'a CStr> for FuriString {
fn extend<T: IntoIterator<Item = &'a CStr>>(&mut self, iter: T) {
iter.into_iter().for_each(move |s| self.push_c_str(s));
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl Extend<Box<str>> for FuriString {
fn extend<T: IntoIterator<Item = Box<str>>>(&mut self, iter: T) {
iter.into_iter().for_each(move |s| self.push_str(&s));
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> Extend<Cow<'a, str>> for FuriString {
fn extend<T: IntoIterator<Item = Cow<'a, str>>>(&mut self, iter: T) {
iter.into_iter().for_each(move |s| self.push_str(&s));
}
}
impl FromIterator<FuriString> for FuriString {
#[allow(tail_expr_drop_order)]
fn from_iter<T: IntoIterator<Item = FuriString>>(iter: T) -> Self {
let mut iterator = iter.into_iter();
match iterator.next() {
None => FuriString::new(),
Some(mut buf) => {
buf.extend(iterator);
buf
}
}
}
}
impl FromIterator<char> for FuriString {
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
let mut buf = FuriString::new();
buf.extend(iter);
buf
}
}
impl<'a> FromIterator<&'a char> for FuriString {
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
let mut buf = FuriString::new();
buf.extend(iter);
buf
}
}
impl<'a> FromIterator<&'a str> for FuriString {
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
let mut buf = FuriString::new();
buf.extend(iter);
buf
}
}
impl<'a> FromIterator<&'a CStr> for FuriString {
fn from_iter<T: IntoIterator<Item = &'a CStr>>(iter: T) -> Self {
let mut buf = FuriString::new();
buf.extend(iter);
buf
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl FromIterator<Box<str>> for FuriString {
fn from_iter<T: IntoIterator<Item = Box<str>>>(iter: T) -> Self {
let mut buf = FuriString::new();
buf.extend(iter);
buf
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> FromIterator<Cow<'a, str>> for FuriString {
fn from_iter<T: IntoIterator<Item = Cow<'a, str>>>(iter: T) -> Self {
let mut buf = FuriString::new();
buf.extend(iter);
buf
}
}
impl Add<&str> for FuriString {
type Output = FuriString;
#[inline]
fn add(mut self, rhs: &str) -> Self::Output {
self.push_str(rhs);
self
}
}
impl AddAssign<&str> for FuriString {
#[inline]
fn add_assign(&mut self, rhs: &str) {
self.push_str(rhs);
}
}
impl PartialEq for FuriString {
fn eq(&self, other: &Self) -> bool {
unsafe { sys::furi_string_equal(self.0.as_ptr(), other.0.as_ptr()) }
}
}
impl PartialEq<CStr> for FuriString {
fn eq(&self, other: &CStr) -> bool {
unsafe { sys::furi_string_equal_str(self.0.as_ptr(), other.as_ptr()) }
}
}
impl PartialEq<FuriString> for CStr {
fn eq(&self, other: &FuriString) -> bool {
other.eq(self)
}
}
impl PartialEq<str> for FuriString {
fn eq(&self, other: &str) -> bool {
self.to_bytes().eq(other.as_bytes())
}
}
impl PartialEq<FuriString> for str {
fn eq(&self, other: &FuriString) -> bool {
other.eq(self)
}
}
impl PartialEq<&str> for FuriString {
fn eq(&self, other: &&str) -> bool {
self.eq(*other)
}
}
impl PartialEq<FuriString> for &str {
fn eq(&self, other: &FuriString) -> bool {
other.eq(*self)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl PartialEq<CString> for FuriString {
fn eq(&self, other: &CString) -> bool {
self.eq(other.as_c_str())
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl PartialEq<FuriString> for CString {
fn eq(&self, other: &FuriString) -> bool {
other.eq(self.as_c_str())
}
}
impl Ord for FuriString {
fn cmp(&self, other: &Self) -> Ordering {
match unsafe { sys::furi_string_cmp(self.0.as_ptr(), other.0.as_ptr()) } {
..=-1 => Ordering::Less,
0 => Ordering::Equal,
1.. => Ordering::Greater,
}
}
}
impl PartialOrd for FuriString {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl hash::Hash for FuriString {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.as_c_str().hash(state);
}
}
impl fmt::Debug for FuriString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_char('"')?;
for c in self.chars_lossy() {
f.write_char(c)?;
}
f.write_char('"')
}
}
impl ufmt::uDebug for FuriString {
#[inline]
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
f.write_char('"')?;
for c in self.chars_lossy() {
f.write_char(c)?;
}
f.write_char('"')
}
}
impl fmt::Display for FuriString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in self.chars_lossy() {
f.write_char(c)?;
}
Ok(())
}
}
impl ufmt::uDisplay for FuriString {
#[inline]
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
for c in self.chars_lossy() {
f.write_char(c)?;
}
Ok(())
}
}
impl Write for FuriString {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
#[inline]
fn write_char(&mut self, c: char) -> fmt::Result {
self.push(c);
Ok(())
}
}
impl ufmt::uWrite for FuriString {
type Error = Infallible;
#[inline]
fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
self.push_str(s);
Ok(())
}
#[inline]
fn write_char(&mut self, c: char) -> Result<(), Self::Error> {
self.push(c);
Ok(())
}
}
#[flipperzero_test::tests]
mod tests {
use super::*;
#[test]
fn invalid_utf8_is_replaced() {
let d: [u8; 3] = [0x66, 0xfc, 0x72];
let s = FuriString::new();
for b in d {
unsafe { sys::furi_string_push_back(s.0.as_ptr(), b as c_char) };
}
for (l, r) in s.chars_lossy().zip("f�r".chars()) {
assert_eq!(l, r);
}
}
}