use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Range;
use std::str::{from_utf8_unchecked, FromStr};
pub mod container;
pub mod encoding;
mod error;
pub mod span;
use container::{Container, Recommended};
use encoding::{decode, encode};
pub use error::{Error, Result};
use span::{init, Span};
#[derive(Clone)]
pub struct Format<const N: usize, C = Recommended>
where
C: Container,
{
value: C,
spans: [Span; N],
flags: u64,
}
impl<const N: usize, C> Format<N, C>
where
C: Container,
{
#[must_use]
pub fn new() -> Self {
debug_assert!(N <= 64, "span count must be <= 64");
Self {
value: C::from(&[b':'; N][1..]), spans: init::<N>(),
flags: 0,
}
}
pub fn get(&self, index: usize) -> Cow<str> {
let range: Range<_> = self.spans[index].into();
if self.flags & (1 << index) == 0 {
unsafe { Cow::Borrowed(from_utf8_unchecked(&self.value[range])) }
} else {
decode(&self.value[range])
}
}
pub fn set<S>(&mut self, index: usize, value: S) -> Result
where
S: AsRef<[u8]>,
{
let value = encode(value.as_ref());
match value {
Cow::Borrowed(_) => self.flags &= !(1 << index),
Cow::Owned(_) => self.flags |= 1 << index,
}
self.value.splice(self.spans[index], value.as_ref());
let by = i16::try_from(value.len())
.ok()
.and_then(|len| len.checked_sub_unsigned(self.spans[index].len()))
.ok_or(Error::Length)?;
self.spans[index].shift_end(by)?;
for i in (index + 1)..N {
self.spans[i].shift(by)?;
}
Ok(())
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { from_utf8_unchecked(&self.value) }
}
}
impl<const N: usize, C> FromStr for Format<N, C>
where
C: Container,
{
type Err = Error;
fn from_str(value: &str) -> Result<Self> {
let mut format = Format::new();
format.value = C::from(value.as_bytes());
let mut start = 0u16;
let mut index = 0;
let mut shift = 1;
for (i, char) in value.char_indices() {
match char {
':' => {
let end = u16::try_from(i).map_err(|_| Error::Length)?;
format.spans[index] = Span::new(start, end);
index += 1;
start = end + 1;
shift = 1 << index;
}
'%' if format.flags & shift == 0 => {
let bytes = value.as_bytes();
if let Some(&[b1, b2]) = bytes.get(i + 1..i + 3) {
if b1.is_ascii_hexdigit() && b2.is_ascii_hexdigit() {
format.flags |= shift;
}
}
}
_ => {}
}
}
let end = u16::try_from(value.len()).map_err(|_| Error::Length)?;
format.spans[index] = Span::new(start, end);
if index == N - 1 {
Ok(format)
} else {
Err(Error::Cardinality)
}
}
}
impl<const N: usize, C> Hash for Format<N, C>
where
C: Container,
{
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<const N: usize, C> PartialEq for Format<N, C>
where
C: Container + Eq,
{
#[inline]
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<const N: usize, C> Eq for Format<N, C> where C: Container + Eq {}
impl<const N: usize, C> PartialOrd for Format<N, C>
where
C: Container + Ord,
{
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<const N: usize, C> Ord for Format<N, C>
where
C: Container + Ord,
{
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}
impl<const N: usize, C> Default for Format<N, C>
where
C: Container,
{
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<const N: usize, C> fmt::Display for Format<N, C>
where
C: Container,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<const N: usize, C> fmt::Debug for Format<N, C>
where
C: Container,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Format")
.field("value", &self.as_str())
.field("spans", &self.spans)
.field("flags", &self.flags)
.finish()
}
}