kvarn_utils/
extensions.rsuse crate::{
chars::{CR, LF, SPACE},
str, Arc, Bytes, Debug,
};
pub const PRESENT_INTERNAL_PREFIX: &[u8] = b"!> ";
pub const PRESENT_INTERNAL_AND: &[u8] = b" &> ";
pub const PRESENT_INTERNAL_AND_TRIMMED: &[u8] = b"&>";
#[derive(Debug)]
struct PresentExtensionPosData {
name_start: usize,
name_len: usize,
arg_start: usize,
arg_len: usize,
}
impl PresentExtensionPosData {
fn from_name_and_arg(name: (usize, usize), arg: (usize, usize)) -> Self {
Self {
name_start: name.0,
name_len: name.1,
arg_start: arg.0,
arg_len: arg.1,
}
}
fn get_name(&self) -> (usize, usize) {
(self.name_start, self.name_len)
}
fn get_arg(&self) -> (usize, usize) {
(self.arg_start, self.arg_len)
}
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone)]
#[must_use]
pub struct PresentExtensions {
data: Bytes,
extensions: Arc<Vec<PresentExtensionPosData>>,
data_start: usize,
}
impl PresentExtensions {
pub fn new(data: Bytes) -> Option<Self> {
let mut extensions_args = Vec::new();
if !data.starts_with(PRESENT_INTERNAL_PREFIX)
|| data[PRESENT_INTERNAL_PREFIX.len()..].starts_with(PRESENT_INTERNAL_AND)
{
return None;
}
let mut start = PRESENT_INTERNAL_PREFIX.len();
let mut last_name = None;
let mut has_cr = false;
for (pos, byte) in data
.iter()
.copied()
.enumerate()
.skip(PRESENT_INTERNAL_PREFIX.len())
{
if start > pos {
continue;
}
if byte == SPACE || byte == CR || byte == LF {
str::from_utf8(&data[start..pos]).ok()?;
let len = pos - start;
let span = (start, len);
if len > 0 && &data[start..pos] != PRESENT_INTERNAL_AND_TRIMMED {
#[allow(clippy::option_if_let_else)]
if let Some(name) = last_name {
extensions_args
.push(PresentExtensionPosData::from_name_and_arg(name, span));
} else {
last_name = Some((start, len));
extensions_args
.push(PresentExtensionPosData::from_name_and_arg(span, span));
}
}
if byte == CR {
has_cr = true;
}
if byte == LF {
return Some(Self {
data,
extensions: Arc::new(extensions_args),
data_start: pos + if has_cr { 2 } else { 1 },
});
}
start = if data
.get(pos..)
.map_or(false, |range| range.starts_with(PRESENT_INTERNAL_AND))
{
last_name = None;
pos + PRESENT_INTERNAL_AND.len()
} else {
pos + 1
};
}
}
None
}
pub fn empty() -> Self {
Self {
data: Bytes::new(),
extensions: Arc::new(Vec::new()),
data_start: 0,
}
}
#[inline]
pub fn iter_clone(&self) -> PresentExtensionsIter {
PresentExtensionsIter {
data: Self::clone(self),
index: 0,
}
}
#[inline]
pub fn data_start(&self) -> usize {
self.data_start
}
}
impl IntoIterator for PresentExtensions {
type Item = PresentArguments;
type IntoIter = PresentExtensionsIter;
fn into_iter(self) -> Self::IntoIter {
PresentExtensionsIter {
data: self,
index: 0,
}
}
}
#[derive(Debug)]
pub struct PresentExtensionsIter {
data: PresentExtensions,
index: usize,
}
impl Iterator for PresentExtensionsIter {
type Item = PresentArguments;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let start = self.index;
if start == self.data.extensions.len() {
return None;
}
let name = self.data.extensions[start].get_name();
let iter = self.data.extensions[start + 1..].iter();
let mut different_name = false;
for current in iter {
self.index += 1;
if current.get_name() != name {
different_name = true;
break;
}
}
if self.index + 1 == self.data.extensions.len() && !different_name {
self.index += 1;
};
Some(PresentArguments {
data: PresentExtensions::clone(&self.data),
data_index: start,
len: self.index - start,
})
}
}
#[derive(Debug)]
#[must_use]
pub struct PresentArguments {
data: PresentExtensions,
data_index: usize,
len: usize,
}
impl PresentArguments {
#[inline]
pub fn empty() -> Self {
Self {
data: PresentExtensions::empty(),
data_index: 0,
len: 0,
}
}
#[inline]
pub fn name(&self) -> &str {
let (start, len) = self.data.extensions[self.data_index].get_name();
unsafe { str::from_utf8_unchecked(&self.data.data[start..start + len]) }
}
#[inline]
pub fn iter(&self) -> PresentArgumentsIter<'_> {
PresentArgumentsIter {
data: &self.data,
data_index: self.data_index,
back_index: self.len,
index: 1,
}
}
}
impl<'a> IntoIterator for &'a PresentArguments {
type Item = &'a str;
type IntoIter = PresentArgumentsIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug)]
pub struct PresentArgumentsIter<'a> {
data: &'a PresentExtensions,
data_index: usize,
back_index: usize,
index: usize,
}
impl<'a> Iterator for PresentArgumentsIter<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.back_index {
return None;
}
let (start, len) = self.data.extensions[self.data_index + self.index].get_arg();
self.index += 1;
Some(unsafe { str::from_utf8_unchecked(&self.data.data[start..start + len]) })
}
}
impl DoubleEndedIterator for PresentArgumentsIter<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.index == self.back_index {
return None;
}
let (start, len) = self.data.extensions[self.data_index + self.back_index - 1].get_arg();
self.back_index -= 1;
Some(unsafe { str::from_utf8_unchecked(&self.data.data[start..start + len]) })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic() {
let file = "\
!> tmpl standard.html md.html &> allow-ips 10.0.0.16 &>
File's contents.
";
let mut extensions = PresentExtensions::new(Bytes::from_static(file.as_bytes()))
.unwrap()
.into_iter();
{
let extension = extensions.next().unwrap();
assert_eq!(extension.name(), "tmpl");
let mut arguments = extension.iter();
assert_eq!(arguments.next(), Some("standard.html"));
assert_eq!(arguments.next(), Some("md.html"));
assert_eq!(arguments.next(), None);
}
{
let extension = extensions.next().unwrap();
assert_eq!(extension.name(), "allow-ips");
let mut arguments = extension.iter();
assert_eq!(arguments.next(), Some("10.0.0.16"));
assert_eq!(arguments.next(), None);
}
assert!(extensions.next().is_none());
}
#[test]
fn weird() {
let file = "\
!> tmpl standard.html md.html &>
File's contents.
";
let mut extensions = PresentExtensions::new(Bytes::from_static(file.as_bytes()))
.unwrap()
.into_iter();
{
let extension = extensions.next().unwrap();
assert_eq!(extension.name(), "tmpl");
let mut arguments = extension.iter();
assert_eq!(arguments.next(), Some("standard.html"));
assert_eq!(arguments.next(), Some("md.html"));
assert_eq!(arguments.next(), None);
}
assert!(extensions.next().is_none());
}
}