Skip to content

Commit f9d8fe9

Browse files
committed
Add trait Into(Mut)AsciiStr to replace and AsciiCast<Target=AsciiStr>
Supports converting partially, and The error type says where and why it wen't wrong, and partial conversion is also possible. ascii_part() is a bad name, maybe partially_into_ascii()? Separate trait to follow std convention. Untested.
1 parent abbd854 commit f9d8fe9

File tree

2 files changed

+191
-4
lines changed

2 files changed

+191
-4
lines changed

src/ascii_str.rs

Lines changed: 190 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{fmt, mem};
22
use std::ops::{Index, IndexMut, Range, RangeTo, RangeFrom, RangeFull};
3+
use std::error::Error;
34
use std::ascii::AsciiExt;
45

56
use AsciiCast;
@@ -77,13 +78,28 @@ impl AsciiStr {
7778
{
7879
unsafe {
7980
if bytes.as_ref().is_ascii() {
80-
Ok( mem::transmute(bytes.as_ref()) )
81+
Ok( Self::from_bytes_unchecked(bytes) )
8182
} else {
8283
Err(())
8384
}
8485
}
8586
}
8687

88+
/// Converts anything that can represent a byte slice into an `AsciiStr` without validation.
89+
///
90+
/// # Examples
91+
///
92+
/// ```
93+
/// # use ascii::AsciiStr;
94+
/// let foo = unsafe{ AsciiStr::from_bytes_unchecked("foo") };
95+
/// assert_eq!(foo.as_str(), "foo");
96+
/// ```
97+
pub unsafe fn from_bytes_unchecked<'a, B: ?Sized>(bytes: &'a B) -> &'a AsciiStr
98+
where B: AsRef<[u8]>
99+
{
100+
mem::transmute(bytes.as_ref())
101+
}
102+
87103
/// Converts a borrowed string to a borrows ascii string.
88104
pub fn from_str<'a>(s: &'a str) -> Result<&'a AsciiStr, ()> {
89105
AsciiStr::from_bytes(s.as_bytes())
@@ -322,10 +338,181 @@ impl<'a> AsciiCast<'a> for str {
322338
}
323339
}
324340

341+
342+
/// Error returned by IntoAsciiStr
343+
#[derive(PartialEq)]// allows assert_eq!
344+
pub struct IntoAsciiStrError {
345+
index: usize,
346+
/// If less than 128, it was a byte >= 128
347+
not_ascii: char,
348+
}
349+
350+
impl fmt::Debug for IntoAsciiStrError {
351+
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
352+
if (self.not_ascii as u32) < 128 {
353+
write!(fmtr, "b'\\x{:x}' at index {}", self.not_ascii as u8 + 128, self.index)
354+
} else {
355+
write!(fmtr, "'{}' at index {}", self.not_ascii, self.index)
356+
}
357+
}
358+
}
359+
impl fmt::Display for IntoAsciiStrError {
360+
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
361+
if (self.not_ascii as u32) < 128 {
362+
write!(fmtr, "the byte \\x{:x} at index {} is not ASCII", self.not_ascii as u8 + 128, self.index)
363+
} else {
364+
write!(fmtr, "the character {} at index {} is not ASCII", self.not_ascii, self.index)
365+
}
366+
}
367+
}
368+
impl Error for IntoAsciiStrError {
369+
fn description(&self) -> &'static str {
370+
"one or more bytes (or chars if str) are not ASCII"
371+
}
372+
}
373+
374+
375+
/// Trait for converting various slices into `AsciiStr`.
376+
pub trait IntoAsciiStr : AsciiExt {
377+
/// Convert to `AsciiStr`, not doing any range asserts.
378+
unsafe fn into_ascii_unchecked(&self) -> &AsciiStr;
379+
/// Convert the portion from start of self that is valid ASCII to `AsciiStr`.
380+
fn ascii_part(&self) -> &AsciiStr;
381+
/// Convert to `AsciiStr`.
382+
fn into_ascii(&self) -> Result<&AsciiStr,IntoAsciiStrError>;
383+
}
384+
/// Trait for converting various slices into `AsciiStr`.
385+
pub trait IntoMutAsciiStr : AsciiExt {
386+
/// Convert to `AsciiStr`, not doing any range asserts.
387+
unsafe fn into_ascii_mut_unchecked(&mut self) -> &mut AsciiStr;
388+
/// Convert the portion from start of self that is valid ASCII to `AsciiStr`.
389+
fn ascii_part_mut(&mut self) -> &mut AsciiStr;
390+
/// Convert to `AsciiStr`.
391+
fn into_ascii_mut(&mut self) -> Result<&mut AsciiStr,IntoAsciiStrError>;
392+
}
393+
394+
#[cfg(feature = "unstable")]
395+
impl IntoAsciiStr for AsciiStr {
396+
fn into_ascii(&self) -> Result<&AsciiStr,IntoAsciiStrError> {
397+
Ok(self)
398+
}
399+
fn ascii_part(&self) -> &AsciiStr {
400+
self
401+
}
402+
unsafe fn into_ascii_unchecked(&self) -> &AsciiStr {
403+
self
404+
}
405+
}
406+
#[cfg(feature = "unstable")]
407+
impl IntoMutAsciiStr for AsciiStr {
408+
fn into_ascii_mut(&mut self) -> Result<&mut AsciiStr,IntoAsciiStrError> {
409+
Ok(self)
410+
}
411+
fn ascii_part_mut(&mut self) -> &mut AsciiStr {
412+
self
413+
}
414+
unsafe fn into_ascii_mut_unchecked(&mut self) -> &mut AsciiStr {
415+
self
416+
}
417+
}
418+
419+
impl IntoAsciiStr for [u8] {
420+
fn into_ascii(&self) -> Result<&AsciiStr,IntoAsciiStrError> {
421+
match self.iter().enumerate().find(|&(_,b)| *b > 127 ) {
422+
Some((index, &byte)) => Err(IntoAsciiStrError{
423+
index: index,
424+
not_ascii: (byte - 128) as char,
425+
}),
426+
None => unsafe{ Ok(self.into_ascii_unchecked()) },
427+
}
428+
}
429+
fn ascii_part(&self) -> &AsciiStr {
430+
let ascii = self.iter().position(|b| *b > 127 )
431+
.map(|l| &self[..l] )
432+
.unwrap_or(self);
433+
unsafe{ ascii.into_ascii_unchecked() }
434+
}
435+
unsafe fn into_ascii_unchecked(&self) -> &AsciiStr {
436+
AsciiStr::from_bytes_unchecked(self)
437+
}
438+
}
439+
impl IntoMutAsciiStr for [u8] {
440+
fn into_ascii_mut(&mut self) -> Result<&mut AsciiStr,IntoAsciiStrError> {
441+
match self.iter().enumerate().find(|&(_,b)| *b > 127 ) {
442+
Some((index, &byte)) => Err(IntoAsciiStrError{
443+
index: index,
444+
not_ascii: (byte - 128) as char,
445+
}),
446+
None => unsafe{ Ok(self.into_ascii_mut_unchecked()) },
447+
}
448+
}
449+
fn ascii_part_mut(&mut self) -> &mut AsciiStr {
450+
let ascii = match self.iter_mut().position(|b| *b > 127 ) {
451+
Some(l) => &mut self[..l],
452+
None => self,
453+
};
454+
unsafe{ ascii.into_ascii_mut_unchecked() }
455+
}
456+
unsafe fn into_ascii_mut_unchecked(&mut self) -> &mut AsciiStr {
457+
mem::transmute(self)
458+
}
459+
}
460+
461+
impl IntoAsciiStr for str {
462+
fn into_ascii(&self) -> Result<&AsciiStr,IntoAsciiStrError> {
463+
let s = self.ascii_part();
464+
if s.len() == self.len() {
465+
Ok(s)
466+
} else {
467+
Err(IntoAsciiStrError{
468+
index: s.len(),
469+
not_ascii: self[s.len()..].chars().next().unwrap(),
470+
})
471+
}
472+
}
473+
fn ascii_part(&self) -> &AsciiStr {
474+
self.as_bytes().ascii_part()
475+
}
476+
unsafe fn into_ascii_unchecked(&self) -> &AsciiStr {
477+
mem::transmute(self)
478+
}
479+
}
480+
impl IntoMutAsciiStr for str {
481+
fn into_ascii_mut(&mut self) -> Result<&mut AsciiStr,IntoAsciiStrError> {
482+
match self.bytes().enumerate().find(|&(_,b)| b > 127 ) {
483+
Some((index, byte)) => Err(IntoAsciiStrError{
484+
index: index,
485+
not_ascii: (byte - 128) as char,
486+
}),
487+
None => unsafe{ Ok(self.into_ascii_mut_unchecked()) },
488+
}
489+
}
490+
fn ascii_part_mut(&mut self) -> &mut AsciiStr {
491+
unsafe{ match self.bytes().position(|b| b > 127 ) {
492+
Some(index) => self.slice_mut_unchecked(0, index).into_ascii_mut_unchecked(),
493+
None => self.into_ascii_mut_unchecked(),
494+
}}
495+
}
496+
unsafe fn into_ascii_mut_unchecked(&mut self) -> &mut AsciiStr {
497+
mem::transmute(self)
498+
}
499+
}
500+
501+
502+
325503
#[cfg(test)]
326504
mod tests {
327-
use AsciiCast;
328-
use super::AsciiStr;
505+
use {AsciiCast,Ascii};
506+
use super::{AsciiStr,IntoAsciiStr,IntoAsciiStrError};
507+
508+
#[test]
509+
fn into_ascii() {
510+
fn generic<C:IntoAsciiStr+?Sized>(c: &C) -> Result<&AsciiStr,IntoAsciiStrError> {
511+
c.into_ascii()
512+
}
513+
assert_eq!(generic("A"), Ok([Ascii::A].as_ref().into()));
514+
assert_eq!(generic(&b"A"[..]), Ok([Ascii::A].as_ref().into()));
515+
}
329516

330517
#[test]
331518
fn as_str() {

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use std::ascii::AsciiExt;
2323

2424
pub use ascii::{Ascii, IntoAscii, IntoAsciiError};
2525
pub use ascii_string::{AsciiString, IntoAsciiString};
26-
pub use ascii_str::AsciiStr;
26+
pub use ascii_str::{AsciiStr, IntoAsciiStr, IntoMutAsciiStr, IntoAsciiStrError};
2727

2828
/// Trait for converting into an ascii type.
2929
pub trait AsciiCast<'a>: AsciiExt {

0 commit comments

Comments
 (0)