use std::borrow::Cow;
use std::ffi::{c_char, c_int, OsStr};
use std::mem::ManuallyDrop;
use std::path::PathBuf;
use std::string::{self, String as StdString};
use std::{fmt, slice, str};
use lua::{ffi::*, Poppable, Pushable};
use luajit_bindings as lua;
use crate::NonOwning;
#[derive(Eq, Ord, PartialOrd, Hash)]
#[repr(C)]
pub struct String {
pub(crate) data: *mut c_char,
pub(crate) size: usize,
}
impl String {
#[inline]
pub fn new() -> Self {
Self { data: std::ptr::null_mut(), size: 0 }
}
#[inline]
pub fn from_bytes(mut vec: Vec<u8>) -> Self {
vec.reserve_exact(1);
Vec::push(&mut vec, 0);
let size = vec.len() - 1;
let data = vec.leak().as_mut_ptr() as *mut c_char;
Self { data, size }
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub const fn len(&self) -> usize {
self.size
}
#[inline]
pub const fn as_ptr(&self) -> *const c_char {
self.data as *const c_char
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
if self.data.is_null() {
&[]
} else {
unsafe { slice::from_raw_parts(self.data as *const u8, self.size) }
}
}
#[inline]
pub fn as_str(&self) -> Result<&str, str::Utf8Error> {
str::from_utf8(self.as_bytes())
}
#[inline]
pub fn to_string_lossy(&self) -> Cow<'_, str> {
StdString::from_utf8_lossy(self.as_bytes())
}
#[inline]
pub fn into_bytes(self) -> Vec<u8> {
if self.data.is_null() {
Vec::new()
} else {
unsafe {
let mdrop = ManuallyDrop::new(self);
Vec::from_raw_parts(
mdrop.data.cast::<u8>(),
mdrop.size,
mdrop.size,
)
}
}
}
#[inline]
pub fn into_string(self) -> Result<StdString, string::FromUtf8Error> {
StdString::from_utf8(self.into_bytes())
}
#[inline]
#[doc(hidden)]
pub fn non_owning(&self) -> NonOwning<'_, String> {
NonOwning::new(Self { ..*self })
}
}
impl Default for String {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for String {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for String {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.to_string_lossy())
}
}
impl Clone for String {
fn clone(&self) -> Self {
Self::from_bytes(self.as_bytes().to_owned())
}
}
impl Drop for String {
fn drop(&mut self) {
let _ = unsafe {
Vec::from_raw_parts(self.data, self.size + 1, self.size + 1)
};
}
}
impl From<StdString> for String {
#[inline]
fn from(string: StdString) -> Self {
Self::from_bytes(string.into_bytes())
}
}
impl From<&str> for String {
#[inline]
fn from(str: &str) -> Self {
Self::from_bytes(str.as_bytes().to_owned())
}
}
impl From<char> for String {
#[inline]
fn from(ch: char) -> Self {
ch.to_string().into()
}
}
impl From<Cow<'_, str>> for String {
#[inline]
fn from(moo: Cow<'_, str>) -> Self {
moo.into_owned().into()
}
}
impl From<Vec<u8>> for String {
#[inline]
fn from(vec: Vec<u8>) -> Self {
Self::from_bytes(vec)
}
}
impl From<PathBuf> for String {
#[inline]
fn from(path: PathBuf) -> Self {
path.display().to_string().into()
}
}
#[cfg(not(windows))]
impl From<String> for PathBuf {
#[inline]
fn from(nstr: String) -> Self {
use std::os::unix::ffi::OsStrExt;
OsStr::from_bytes(nstr.as_bytes()).to_owned().into()
}
}
#[cfg(windows)]
impl From<String> for PathBuf {
#[inline]
fn from(nstr: String) -> Self {
StdString::from_utf8_lossy(nstr.as_bytes()).into_owned().into()
}
}
impl PartialEq<Self> for String {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl PartialEq<str> for String {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl PartialEq<&str> for String {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl PartialEq<StdString> for String {
#[inline]
fn eq(&self, other: &StdString) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl TryFrom<String> for StdString {
type Error = std::string::FromUtf8Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
StdString::from_utf8(s.into_bytes())
}
}
impl Pushable for String {
unsafe fn push(self, lstate: *mut lua_State) -> Result<c_int, lua::Error> {
lua::ffi::lua_pushlstring(lstate, self.as_ptr(), self.len());
Ok(1)
}
}
impl Poppable for String {
unsafe fn pop(lstate: *mut lua_State) -> Result<Self, lua::Error> {
<StdString as Poppable>::pop(lstate).map(Into::into)
}
}
#[cfg(feature = "serde")]
mod serde {
use std::fmt;
use serde::de::{self, Deserialize, Deserializer, Visitor};
impl<'de> Deserialize<'de> for super::String {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct StringVisitor;
impl<'de> Visitor<'de> for StringVisitor {
type Value = crate::String;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("either a string of a byte vector")
}
fn visit_bytes<E>(self, b: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(crate::String::from_bytes(b.to_owned()))
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(crate::String::from(s))
}
}
deserializer.deserialize_str(StringVisitor)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn partial_eq() {
let lhs = String::from("foo bar baz");
let rhs = String::from("foo bar baz");
assert_eq!(lhs, rhs);
let lhs = String::from("foo bar baz");
let rhs = String::from("bar foo baz");
assert_ne!(lhs, rhs);
let lhs = String::from("€");
let rhs = "€";
assert_eq!(lhs, rhs);
}
#[test]
fn clone() {
let lhs = String::from("abc");
let rhs = lhs.clone();
assert_eq!(lhs, rhs);
}
#[test]
fn from_string() {
let foo = StdString::from("foo bar baz");
let lhs = String::from(foo.as_ref());
let rhs = String::from(foo);
assert_eq!(lhs, rhs);
}
#[test]
fn to_bytes() {
let s = String::from("hello");
let bytes = s.into_bytes();
assert_eq!(&[104, 101, 108, 108, 111][..], &bytes[..]);
}
}