mirror of
https://github.com/Zenithsiz/ndsz.git
synced 2026-02-03 17:52:19 +00:00
Moved zutil's AsciiStrArr to ndsz-util.
This commit is contained in:
parent
95332b131e
commit
55291d01f4
@ -8,6 +8,8 @@ members = [
|
||||
"ndsz-mknds",
|
||||
"ndsz-unnds",
|
||||
"ndsz-unnarc",
|
||||
"ndsz-bytes",
|
||||
"ndsz-util",
|
||||
]
|
||||
|
||||
[patch."https://github.com/Zenithsiz/zutil"]
|
||||
|
||||
@ -9,6 +9,7 @@ version = "0.0.0"
|
||||
|
||||
# Ndsz
|
||||
ndsz-bytes = {path = "../ndsz-bytes"}
|
||||
ndsz-util = {path = "../ndsz-util"}
|
||||
|
||||
# Bytes
|
||||
byteorder = "1.4.3"
|
||||
|
||||
@ -7,7 +7,7 @@ pub mod visitor;
|
||||
pub use visitor::Visitor;
|
||||
|
||||
// Imports
|
||||
use zutil::AsciiStrArr;
|
||||
use ndsz_util::AsciiStrArr;
|
||||
|
||||
/// Directory
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! Directory visitor
|
||||
|
||||
// Imports
|
||||
use zutil::AsciiStrArr;
|
||||
use ndsz_util::AsciiStrArr;
|
||||
|
||||
/// A directory visitor
|
||||
pub trait Visitor {
|
||||
|
||||
@ -9,8 +9,8 @@ pub use self::error::FromReaderError;
|
||||
// Imports
|
||||
use {
|
||||
byteorder::{LittleEndian, ReadBytesExt},
|
||||
ndsz_util::AsciiStrArr,
|
||||
std::io,
|
||||
zutil::AsciiStrArr,
|
||||
};
|
||||
|
||||
/// Sub table entry
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! Errors
|
||||
|
||||
// Imports
|
||||
use {std::io, zutil::ascii_str_arr};
|
||||
use {ndsz_util::ascii_str_arr, std::io};
|
||||
|
||||
/// Error for [`SubTableEntry::from_reader`](super::SubTableEntry::from_reader)
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@ -9,6 +9,7 @@ version = "0.0.0"
|
||||
|
||||
# Ndsz
|
||||
ndsz-bytes = {path = "../ndsz-bytes"}
|
||||
ndsz-util = {path = "../ndsz-util"}
|
||||
|
||||
# Bytes
|
||||
byteorder = "1.4.3"
|
||||
|
||||
@ -10,7 +10,7 @@ pub use error::FromBytesError;
|
||||
use {
|
||||
crate::UnitCode,
|
||||
byteorder::{ByteOrder, LittleEndian},
|
||||
zutil::{ascii_str_arr::AsciiChar, AsciiStrArr},
|
||||
ndsz_util::{ascii_str_arr::AsciiChar, AsciiStrArr},
|
||||
};
|
||||
|
||||
/// Header
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! Errors
|
||||
|
||||
// Imports
|
||||
use zutil::ascii_str_arr;
|
||||
use ndsz_util::ascii_str_arr;
|
||||
|
||||
/// Error for [`Header::from_bytes`](super::Header::from_bytes)
|
||||
#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
|
||||
|
||||
@ -10,6 +10,7 @@ version = "0.0.0"
|
||||
# Ndsz
|
||||
ndsz-fat = {path = "../ndsz-fat"}
|
||||
ndsz-narc = {path = "../ndsz-narc"}
|
||||
ndsz-util = {path = "../ndsz-util"}
|
||||
|
||||
# Cmd
|
||||
clap = {version = "3.2.17", features = ["derive"]}
|
||||
|
||||
@ -13,9 +13,10 @@ use {
|
||||
clap::Parser,
|
||||
ndsz_fat::{dir, Dir, FileAllocationTable},
|
||||
ndsz_narc::Narc,
|
||||
ndsz_util::AsciiStrArr,
|
||||
std::{fs, io, path::PathBuf},
|
||||
tracing_subscriber::prelude::*,
|
||||
zutil::{AsciiStrArr, IoSlice},
|
||||
zutil::IoSlice,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ version = "0.0.0"
|
||||
# Ndsz
|
||||
ndsz-fat = {path = "../ndsz-fat"}
|
||||
ndsz-nds = {path = "../ndsz-nds"}
|
||||
ndsz-util = {path = "../ndsz-util"}
|
||||
|
||||
# Cmd
|
||||
clap = {version = "3.2.17", features = ["derive"]}
|
||||
|
||||
@ -12,13 +12,14 @@ use {
|
||||
anyhow::Context,
|
||||
clap::Parser,
|
||||
ndsz_fat::{dir, Dir, FileAllocationTable, FileNameTable},
|
||||
ndsz_util::AsciiStrArr,
|
||||
std::{
|
||||
fs,
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
},
|
||||
tracing_subscriber::prelude::*,
|
||||
zutil::{AsciiStrArr, IoSlice, ReadByteArray},
|
||||
zutil::{IoSlice, ReadByteArray},
|
||||
};
|
||||
|
||||
|
||||
|
||||
17
ndsz-util/Cargo.toml
Normal file
17
ndsz-util/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "ndsz-util"
|
||||
version = "0.1.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Serde
|
||||
serde = "1.0.145"
|
||||
|
||||
# Error handling
|
||||
thiserror = "1.0.37"
|
||||
|
||||
# Util
|
||||
ascii = "1.1.0"
|
||||
454
ndsz-util/src/ascii_str_arr.rs
Normal file
454
ndsz-util/src/ascii_str_arr.rs
Normal file
@ -0,0 +1,454 @@
|
||||
//! Ascii string backed by an array
|
||||
|
||||
// Modules
|
||||
mod error;
|
||||
mod visitor;
|
||||
|
||||
// Exports
|
||||
pub use {
|
||||
ascii::AsciiChar,
|
||||
error::{FromBytesError, FromUtf8Error, NotAsciiError, TooLongError},
|
||||
};
|
||||
|
||||
// Imports
|
||||
use {
|
||||
ascii::AsciiStr,
|
||||
std::{
|
||||
cmp::Ordering,
|
||||
convert::TryFrom,
|
||||
fmt,
|
||||
hash::Hash,
|
||||
ops::{self, Range},
|
||||
slice::SliceIndex,
|
||||
},
|
||||
visitor::DeserializerVisitor,
|
||||
};
|
||||
|
||||
/// An ascii string backed by an array
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AsciiStrArr<const N: usize> {
|
||||
/// Characters
|
||||
chars: [AsciiChar; N],
|
||||
|
||||
/// Size
|
||||
// Invariant: `self.len <= N`
|
||||
len: usize,
|
||||
}
|
||||
|
||||
// Constructors
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Creates a new empty string
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
chars: [AsciiChar::Null; N],
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// String lengths
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// The capacity of the string
|
||||
pub const CAPACITY: usize = N;
|
||||
|
||||
/// Returns the length of this string
|
||||
#[must_use]
|
||||
pub const fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns the capacity of the string, `N`
|
||||
#[must_use]
|
||||
pub const fn capacity() -> usize {
|
||||
Self::CAPACITY
|
||||
}
|
||||
|
||||
/// Returns if this string is empty
|
||||
#[must_use]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Trims the end of the string from 'ch'
|
||||
pub fn trim_end(&mut self, ch: AsciiChar) {
|
||||
while !self.is_empty() && self.as_ascii().last() == Some(ch) {
|
||||
self.len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a string, trimmed of `ch` on the end
|
||||
#[must_use]
|
||||
pub fn trimmed_end(mut self, ch: AsciiChar) -> Self {
|
||||
self.trim_end(ch);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversions to other string types
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Converts this string to a `&AsciiStr`
|
||||
#[must_use]
|
||||
pub fn as_ascii(&self) -> &AsciiStr {
|
||||
// Get all the initialized elements
|
||||
// Note: `self.len <= N`, so this cannot panic.
|
||||
let chars = self.chars.get(..self.len).expect("Length was larger than `N`");
|
||||
|
||||
<&AsciiStr>::from(chars)
|
||||
}
|
||||
|
||||
/// Converts this string to a `&mut AsciiStr`
|
||||
#[must_use]
|
||||
pub fn as_ascii_mut(&mut self) -> &mut AsciiStr {
|
||||
// Get all the initialized elements
|
||||
// Note: `self.len <= N`, so this cannot panic.
|
||||
let chars = self.chars.get_mut(..self.len).expect("Length was larger than `N`");
|
||||
|
||||
<&mut AsciiStr>::from(chars)
|
||||
}
|
||||
|
||||
/// Converts this string to a `&[AsciiChar]`
|
||||
#[must_use]
|
||||
pub fn as_ascii_slice(&self) -> &[AsciiChar] {
|
||||
self.as_ascii().as_slice()
|
||||
}
|
||||
|
||||
/// Converts this string to a `&mut [AsciiChar]`
|
||||
#[must_use]
|
||||
pub fn as_ascii_slice_mut(&mut self) -> &mut [AsciiChar] {
|
||||
self.as_ascii_mut().as_mut_slice()
|
||||
}
|
||||
|
||||
/// Converts this string to a `&[u8]`
|
||||
#[must_use]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.as_ascii().as_bytes()
|
||||
}
|
||||
|
||||
/// Converts this string to a `&str`
|
||||
#[must_use]
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.as_ascii().as_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversions from other strings
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Creates a string from anything that coerces to `&[AsciiChar]`, including `AsciiStr`
|
||||
pub fn from_ascii<S: ?Sized + AsRef<[AsciiChar]>>(ascii: &S) -> Result<Self, TooLongError<N>> {
|
||||
let ascii = ascii.as_ref();
|
||||
|
||||
// If it has too many elements, return Err
|
||||
if ascii.len() > N {
|
||||
return Err(TooLongError::<N>);
|
||||
}
|
||||
|
||||
// Else create an uninitialized array and copy over the initialized characters
|
||||
let mut chars = [AsciiChar::Null; N];
|
||||
for (uninit, &ascii) in chars.iter_mut().zip(ascii) {
|
||||
*uninit = ascii;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
chars,
|
||||
len: ascii.len(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a string from bytes
|
||||
pub fn from_bytes<B: ?Sized + AsRef<[u8]>>(bytes: &B) -> Result<Self, FromBytesError<N>> {
|
||||
// Get the bytes as ascii first
|
||||
let ascii = AsciiStr::from_ascii(bytes)
|
||||
.map_err(ascii::AsAsciiStrError::valid_up_to)
|
||||
.map_err(|pos| NotAsciiError { pos })
|
||||
.map_err(FromBytesError::NotAscii)?;
|
||||
|
||||
// Then try to convert them
|
||||
Self::from_ascii(ascii).map_err(FromBytesError::TooLong)
|
||||
}
|
||||
|
||||
// Note: No `from_str`, implemented using `FromStr`
|
||||
}
|
||||
|
||||
/// Slicing
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Slices this string, if in bounds
|
||||
#[must_use]
|
||||
pub fn get<I: SliceIndex<[AsciiChar]>>(&self, idx: I) -> Option<&I::Output> {
|
||||
idx.get(&self.chars)
|
||||
}
|
||||
|
||||
/// Slices this string mutably, if in bounds
|
||||
#[must_use]
|
||||
pub fn get_mut<I: SliceIndex<[AsciiChar]>>(&mut self, idx: I) -> Option<&mut I::Output> {
|
||||
idx.get_mut(&mut self.chars)
|
||||
}
|
||||
}
|
||||
|
||||
/// Push/Pop
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Pushes a character onto this string, if there is enough space
|
||||
#[allow(clippy::result_unit_err)] // TODO: An error type for this?
|
||||
pub fn push(&mut self, ch: AsciiChar) -> Result<(), ()> {
|
||||
match self.len == N {
|
||||
true => Err(()),
|
||||
false => {
|
||||
self.chars[self.len] = ch;
|
||||
self.len += 1;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes a string onto this string, if there is enough space
|
||||
#[allow(clippy::result_unit_err)] // TODO: An error type for this?
|
||||
pub fn push_str(&mut self, s: &AsciiStr) -> Result<(), ()> {
|
||||
match self.len + s.len() > N {
|
||||
true => Err(()),
|
||||
false => {
|
||||
self.chars[self.len..(self.len + s.len())].copy_from_slice(s.as_slice());
|
||||
self.len += s.len();
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a character onto the string, if there is enough space
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `idx` is out of bounds.
|
||||
#[allow(clippy::result_unit_err)] // TODO: An error type for this?
|
||||
pub fn insert(&mut self, idx: usize, ch: AsciiChar) -> Result<(), ()> {
|
||||
match self.len == N {
|
||||
true => Err(()),
|
||||
false => {
|
||||
self.chars.copy_within(idx..self.len, idx + 1);
|
||||
self.chars[idx] = ch;
|
||||
self.len += 1;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a range of characters
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `range` is out of bounds.
|
||||
pub fn drain_range(&mut self, range: Range<usize>) {
|
||||
assert!(range.end <= self.len);
|
||||
|
||||
self.chars.copy_within(range.end..self.len, range.start);
|
||||
self.len -= range.end - range.start;
|
||||
}
|
||||
|
||||
/// Replaces all instances of a character with another
|
||||
pub fn replace_inplace(&mut self, from: AsciiChar, to: AsciiChar) {
|
||||
for ch in &mut self.chars[..self.len] {
|
||||
if *ch == from {
|
||||
*ch = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<AsciiStr> for AsciiStrArr<N> {
|
||||
fn as_ref(&self) -> &AsciiStr {
|
||||
self.as_ascii()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsMut<AsciiStr> for AsciiStrArr<N> {
|
||||
fn as_mut(&mut self) -> &mut AsciiStr {
|
||||
self.as_ascii_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<[AsciiChar]> for AsciiStrArr<N> {
|
||||
fn as_ref(&self) -> &[AsciiChar] {
|
||||
self.as_ascii_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsMut<[AsciiChar]> for AsciiStrArr<N> {
|
||||
fn as_mut(&mut self) -> &mut [AsciiChar] {
|
||||
self.as_ascii_slice_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<[u8]> for AsciiStrArr<N> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<str> for AsciiStrArr<N> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
// Note: No `AsMut<[u8]>` nor `AsMut<str>`, as that'd allow for modification
|
||||
// outside of ascii.
|
||||
|
||||
impl<const N: usize> PartialEq for AsciiStrArr<N> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
AsciiStr::eq(self.as_ascii(), other.as_ascii())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Eq for AsciiStrArr<N> {}
|
||||
|
||||
impl<const N: usize> PartialEq<str> for AsciiStrArr<N> {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_str() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> PartialOrd for AsciiStrArr<N> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
AsciiStr::partial_cmp(self.as_ascii(), other.as_ascii())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Ord for AsciiStrArr<N> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
AsciiStr::cmp(self.as_ascii(), other.as_ascii())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Hash for AsciiStrArr<N> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
AsciiStr::hash(self.as_ascii(), state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for AsciiStrArr<N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, const N: usize> ops::Index<I> for AsciiStrArr<N>
|
||||
where
|
||||
I: SliceIndex<[AsciiChar]>,
|
||||
{
|
||||
type Output = <I as SliceIndex<[AsciiChar]>>::Output;
|
||||
|
||||
fn index(&self, idx: I) -> &Self::Output {
|
||||
self.get(idx).expect("Invalid index access")
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, const N: usize> ops::IndexMut<I> for AsciiStrArr<N>
|
||||
where
|
||||
I: SliceIndex<[AsciiChar]>,
|
||||
{
|
||||
fn index_mut(&mut self, idx: I) -> &mut Self::Output {
|
||||
self.get_mut(idx).expect("Invalid index access")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<const N: usize> fmt::Debug for AsciiStrArr<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
AsciiStr::fmt(self.as_ascii(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Display for AsciiStrArr<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
AsciiStr::fmt(self.as_ascii(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const N: usize> serde::Deserialize<'de> for AsciiStrArr<N> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(DeserializerVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> serde::Serialize for AsciiStrArr<N> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
// Serialize as an ascii string
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Generalize this to `impl<const N: usize, const M: usize> From<&[AsciiChar; M]> for AsciiStrArr<N> where M <= N`
|
||||
impl<const N: usize> From<&[AsciiChar; N]> for AsciiStrArr<N> {
|
||||
fn from(src: &[AsciiChar; N]) -> Self {
|
||||
<Self as From<[AsciiChar; N]>>::from(*src)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Generalize this to `impl<const N: usize, const M: usize> From<[AsciiChar; M]> for AsciiStrArr<N> where M <= N`
|
||||
impl<const N: usize> From<[AsciiChar; N]> for AsciiStrArr<N> {
|
||||
fn from(chars: [AsciiChar; N]) -> Self {
|
||||
Self { chars, len: N }
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Generalize this to `impl<const N: usize, const M: usize> TryFrom<&[u8; M]> for AsciiStrArr<N> where M <= N`
|
||||
impl<const N: usize> TryFrom<&[u8; N]> for AsciiStrArr<N> {
|
||||
type Error = NotAsciiError;
|
||||
|
||||
fn try_from(byte_str: &[u8; N]) -> Result<Self, Self::Error> {
|
||||
let mut chars = [AsciiChar::Null; N];
|
||||
|
||||
for (pos, (&byte, ascii)) in byte_str.iter().zip(&mut chars).enumerate() {
|
||||
*ascii = AsciiChar::from_ascii(byte).map_err(|_err| NotAsciiError { pos })?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
chars,
|
||||
len: byte_str.len(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> TryFrom<&AsciiStr> for AsciiStrArr<N> {
|
||||
type Error = TooLongError<N>;
|
||||
|
||||
fn try_from(ascii: &AsciiStr) -> Result<Self, Self::Error> {
|
||||
Self::from_ascii(ascii)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> TryFrom<&[u8]> for AsciiStrArr<N> {
|
||||
type Error = FromBytesError<N>;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> TryFrom<&str> for AsciiStrArr<N> {
|
||||
type Error = FromUtf8Error<N>;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(s.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> TryFrom<&std::ffi::OsStr> for AsciiStrArr<N> {
|
||||
type Error = FromBytesError<N>;
|
||||
|
||||
fn try_from(s: &std::ffi::OsStr) -> Result<Self, Self::Error> {
|
||||
// TODO: Not allocate here, although `OsStr` doesn't provide a `as_bytes` impl, so we can't do much
|
||||
Self::from_bytes(s.to_string_lossy().as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> std::str::FromStr for AsciiStrArr<N> {
|
||||
type Err = FromUtf8Error<N>;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::from_bytes(s.as_bytes())
|
||||
}
|
||||
}
|
||||
32
ndsz-util/src/ascii_str_arr/error.rs
Normal file
32
ndsz-util/src/ascii_str_arr/error.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! Errors
|
||||
|
||||
/// The given string was too long to be converted.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
|
||||
#[derive(thiserror::Error)]
|
||||
#[error("String must be at most {} characters", LEN)]
|
||||
pub struct TooLongError<const LEN: usize>;
|
||||
|
||||
/// The given string has non-ascii characters.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
|
||||
#[derive(thiserror::Error)]
|
||||
#[error("Character at pos {pos} was not ascii")]
|
||||
pub struct NotAsciiError {
|
||||
/// Index of the first non-ascii character
|
||||
pub pos: usize,
|
||||
}
|
||||
|
||||
/// Error returned when converting a byte string to an [`AsciiStrArr`](super::AsciiStrArr).
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
|
||||
#[derive(thiserror::Error)]
|
||||
pub enum FromBytesError<const LEN: usize> {
|
||||
/// Too long
|
||||
#[error("String was too long")]
|
||||
TooLong(TooLongError<LEN>),
|
||||
|
||||
/// Not ascii
|
||||
#[error("String contained non-ascii characters")]
|
||||
NotAscii(NotAsciiError),
|
||||
}
|
||||
|
||||
/// Error returned when converting a utf-8 [`String`] to an [`AsciiStrArr`](super::AsciiStrArr).
|
||||
pub type FromUtf8Error<const LEN: usize> = FromBytesError<LEN>;
|
||||
26
ndsz-util/src/ascii_str_arr/visitor.rs
Normal file
26
ndsz-util/src/ascii_str_arr/visitor.rs
Normal file
@ -0,0 +1,26 @@
|
||||
//! Visitor for [`AsciiStrArr`]
|
||||
|
||||
// Imports
|
||||
use super::AsciiStrArr;
|
||||
use ascii::AsciiStr;
|
||||
use std::{convert::TryFrom, fmt};
|
||||
|
||||
/// Visitor implementation
|
||||
pub(super) struct DeserializerVisitor<const N: usize>;
|
||||
|
||||
impl<'de, const N: usize> serde::de::Visitor<'de> for DeserializerVisitor<N> {
|
||||
type Value = AsciiStrArr<N>;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO: Maybe get the full string at compile time and use `write_str`
|
||||
f.write_fmt(format_args!("An ascii string of length {} or less", N))
|
||||
}
|
||||
|
||||
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
// Convert it to ascii
|
||||
let ascii_str = AsciiStr::from_ascii(value).map_err(E::custom)?;
|
||||
|
||||
// Try to convert it
|
||||
AsciiStrArr::try_from(ascii_str).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
12
ndsz-util/src/lib.rs
Normal file
12
ndsz-util/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//! Utilities
|
||||
//!
|
||||
//! Most here might be moved to external creates if deemed worth it
|
||||
|
||||
// Features
|
||||
#![feature(slice_index_methods)]
|
||||
|
||||
// Modules
|
||||
pub mod ascii_str_arr;
|
||||
|
||||
// Exports
|
||||
pub use ascii_str_arr::AsciiStrArr;
|
||||
Loading…
x
Reference in New Issue
Block a user