use std::{borrow::Cow, fmt};
use crate::{
arc, define_obj_type, ns,
objc::{self, Obj},
};
#[cfg(feature = "cf")]
use crate::cf;
use super::Class;
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[repr(usize)]
pub enum Encoding {
Ascii = 1,
Utf8 = 4,
MacOsRoman = 30,
}
define_obj_type!(
#[doc(alias = "NSString")]
pub String(ns::Id), NS_STRING
);
define_obj_type!(
#[doc(alias = "NSMutableString")]
pub StringMut(String), NS_MUTABLE_STRING
);
impl arc::A<String> {
#[objc::msg_send(initWithBytes:length:encoding:)]
pub fn init_with_bytes_length_encoding(
self,
bytes: *const u8,
length: usize,
encoding: ns::StringEncoding,
) -> Option<arc::R<String>>;
#[objc::msg_send(initWithBytesNoCopy:length:encoding:freeWhenDone:)]
pub fn init_with_bytes_no_copy_length_encoding_free_when_done(
self,
bytes: *const u8,
length: usize,
encoding: ns::StringEncoding,
free_when_done: bool,
) -> Option<arc::R<String>>;
}
impl String {
#[inline]
pub fn with_str(str: &str) -> arc::R<Self> {
unsafe {
Self::alloc()
.init_with_bytes_length_encoding(str.as_ptr(), str.len(), Encoding::Utf8)
.unwrap_unchecked()
}
}
#[inline]
pub fn with_str_no_copy(str: &str) -> arc::R<Self> {
unsafe {
Self::alloc()
.init_with_bytes_no_copy_length_encoding_free_when_done(
str.as_ptr(),
str.len(),
Encoding::Utf8,
false,
)
.unwrap_unchecked()
}
}
#[objc::msg_send(length)]
pub fn len(&self) -> usize;
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[objc::msg_send(lowercaseString)]
pub fn lowercased(&self) -> arc::R<Self>;
#[objc::msg_send(intValue)]
pub fn to_i32(&self) -> i32;
#[objc::msg_send(longLongValue)]
pub fn to_i64(&self) -> i64;
#[objc::msg_send(floatValue)]
pub fn to_f32(&self) -> f32;
#[objc::msg_send(doubleValue)]
pub fn to_f64(&self) -> f64;
#[objc::msg_send(boolValue)]
pub fn to_bool(&self) -> bool;
#[objc::msg_send(integerValue)]
pub fn to_integer(&self) -> ns::Integer;
#[objc::msg_send(mutableCopy)]
pub fn copy_mut(&self) -> arc::R<ns::StringMut>;
#[objc::msg_send(substringWithRange:)]
pub fn substring_with_range(&self, range: ns::Range) -> arc::R<Self>;
#[inline]
pub fn substring(&self, range: std::ops::Range<usize>) -> arc::R<ns::String> {
self.substring_with_range(range.into())
}
#[objc::msg_send(cStringUsingEncoding:)]
pub fn c_string(&self, encoding: Encoding) -> *const i8;
#[objc::msg_send(lengthOfBytesUsingEncoding:)]
pub fn len_of_bytes(&self, encoding: Encoding) -> ns::UInteger;
#[objc::msg_send(UTF8String)]
pub unsafe fn utf8_chars_ar(&self) -> *const i8;
#[cfg(feature = "cf")]
pub fn as_cf(&self) -> &cf::String {
unsafe { std::mem::transmute(self) }
}
#[objc::msg_send(isEqualToString:)]
pub fn eq_ns_string(&self, other: &Self) -> bool;
#[inline]
pub fn to_selector(&self) -> Option<&'static objc::Sel> {
ns::selector_from_ns_string(self)
}
#[inline]
pub fn to_class(&self) -> Option<&'static objc::Class<ns::Id>> {
ns::class_from_ns_string(self)
}
#[inline]
pub fn from_class<T: Obj>(cls: &objc::Class<T>) -> arc::R<Self> {
ns::string_from_class(cls)
}
#[inline]
pub fn from_selector(sel: &objc::Sel) -> arc::R<Self> {
ns::string_from_selector(sel)
}
}
impl arc::A<StringMut> {
#[objc::msg_send(initWithCapacity:)]
pub fn with_capacity(self, capacity: usize) -> arc::R<StringMut>;
}
impl StringMut {
pub fn with_capacity(capacity: usize) -> arc::R<Self> {
Self::alloc().with_capacity(capacity)
}
#[objc::msg_send(replaceCharactersInRange:withString:)]
pub unsafe fn replace_characters_in_throws(
&mut self,
range: ns::Range,
with_string: &ns::String,
);
pub fn replace_characters_in(
&mut self,
range: ns::Range,
with_string: &ns::String,
) -> ns::ExResult {
ns::try_catch(|| unsafe { self.replace_characters_in_throws(range, with_string) })
}
#[objc::msg_send(insertString:atIndex:)]
pub unsafe fn insert_string_at_throws(&mut self, string: &ns::String, at_index: ns::UInteger);
pub fn insert_string_at(
&mut self,
string: &ns::String,
at_index: ns::UInteger,
) -> ns::ExResult {
ns::try_catch(|| unsafe { self.insert_string_at_throws(string, at_index) })
}
#[objc::msg_send(deleteCharactersInRange:)]
pub fn delete_characters_in_throws(&mut self, range: ns::Range);
pub fn delete_characters_in<'ar>(&mut self, range: ns::Range) -> ns::ExResult {
ns::try_catch(|| self.delete_characters_in_throws(range))
}
#[objc::msg_send(append:)]
pub fn append(&mut self, string: &ns::String);
#[objc::msg_send(setString:)]
pub fn set(&mut self, string: &ns::String);
#[cfg(feature = "cf")]
#[inline]
pub fn as_cf_mut(&self) -> &cf::StringMut {
unsafe { std::mem::transmute(self) }
}
}
#[link(name = "ns", kind = "static")]
unsafe extern "C" {
static NS_STRING: &'static Class<String>;
static NS_MUTABLE_STRING: &'static Class<StringMut>;
}
impl PartialEq<str> for String {
fn eq(&self, other: &str) -> bool {
self.as_cf() == other
}
}
impl fmt::Display for String {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(&Cow::from(self.as_cf()))
}
}
#[macro_export]
macro_rules! nsstr {
($f:literal) => {{ $crate::cfstr!($f).as_ns() }};
}
pub use nsstr as str;
impl AsRef<ns::String> for ns::String {
fn as_ref(&self) -> &ns::String {
self
}
}
#[cfg(feature = "cf")]
impl AsRef<cf::String> for ns::String {
fn as_ref(&self) -> &cf::String {
self.as_cf()
}
}
#[cfg(feature = "cf")]
impl AsRef<cf::Type> for ns::String {
fn as_ref(&self) -> &cf::Type {
self.as_cf().as_type_ref()
}
}
#[cfg(feature = "cf")]
impl AsRef<cf::Type> for ns::StringMut {
fn as_ref(&self) -> &cf::Type {
self.as_cf().as_type_ref()
}
}
impl AsRef<ns::String> for ns::StringMut {
fn as_ref(&self) -> &ns::String {
self
}
}
#[cfg(feature = "cf")]
impl AsRef<cf::String> for ns::StringMut {
fn as_ref(&self) -> &cf::String {
self.as_cf()
}
}
#[cfg(test)]
mod tests {
use crate::{ns, objc::ar_pool};
#[test]
fn basics() {
ar_pool(|| {
let m = ns::StringMut::new();
assert!(m.is_empty());
let s = ns::str!(c"10.5");
assert_eq!(s.len(), 4);
assert!(!s.is_empty());
assert_eq!(s.to_i32(), 10);
assert_eq!(s.to_integer(), 10);
assert_eq!(s.to_f32(), 10.5f32);
assert_eq!(s.to_f64(), 10.5f64);
assert_eq!(s.to_bool(), true);
let sub = s.substring(1..2);
assert_eq!(sub.to_i32(), 0);
assert_eq!(sub.to_bool(), false);
let zero = ns::str!(c"0");
assert_eq!(zero, &sub);
let r = ns::try_catch(|| s.substring(1..10));
assert!(r.is_err());
let _l = s.lowercased();
});
}
#[test]
fn mut_throws() {
let one = ns::str!(c"1");
let mut zero = ns::str!(c"0").copy_mut();
zero.replace_characters_in(ns::Range::new(0, 1), &one)
.unwrap();
zero.replace_characters_in(ns::Range::new(0, 10), &one)
.expect_err("Should be exceptions");
}
#[test]
fn macro_str() {
let s = ns::str!(c"привет");
assert_eq!(12, s.len());
}
}