#![no_std]
#![deny(unsafe_code, missing_docs)]
use core::num::NonZeroUsize;
pub fn lf() -> ByteLineEnding {
ByteLineEnding::new(b'\n')
}
pub fn crlf() -> TwoByteLineEnding {
TwoByteLineEnding::new(b'\r', b'\n')
}
pub trait LineEnding {
fn write_ending(&self, slice: &mut [u8]);
fn len(&self) -> NonZeroUsize;
}
pub struct ByteLineEnding {
byte: u8,
}
impl ByteLineEnding {
pub fn new(byte: u8) -> ByteLineEnding {
ByteLineEnding { byte }
}
}
impl LineEnding for ByteLineEnding {
#[inline]
fn write_ending(&self, slice: &mut [u8]) {
slice[0] = self.byte;
}
#[inline]
fn len(&self) -> NonZeroUsize {
NonZeroUsize::new(1_usize).unwrap()
}
}
pub struct TwoByteLineEnding {
byte0: u8,
byte1: u8,
}
impl TwoByteLineEnding {
pub fn new(byte0: u8, byte1: u8) -> TwoByteLineEnding {
TwoByteLineEnding { byte0, byte1 }
}
}
impl LineEnding for TwoByteLineEnding {
#[inline]
fn write_ending(&self, slice: &mut [u8]) {
slice[0] = self.byte0;
slice[1] = self.byte1;
}
#[inline]
fn len(&self) -> NonZeroUsize {
NonZeroUsize::new(2_usize).unwrap()
}
}
pub struct SliceLineEnding<'a> {
slice: &'a [u8],
}
impl<'a> SliceLineEnding<'a> {
pub fn new(slice: &[u8]) -> Option<SliceLineEnding> {
if slice.is_empty() {
None
} else {
Some(SliceLineEnding { slice })
}
}
}
impl<'a> LineEnding for SliceLineEnding<'a> {
#[inline]
fn write_ending(&self, slice: &mut [u8]) {
slice.copy_from_slice(self.slice);
}
#[inline]
fn len(&self) -> NonZeroUsize {
NonZeroUsize::new(self.slice.len()).expect("Length already checked")
}
}
pub fn line_wrap<L: LineEnding>(
buf: &mut [u8],
input_len: usize,
line_len: usize,
line_ending: &L,
) -> usize {
if input_len <= line_len {
return 0;
}
let line_ending_len = line_ending.len();
let line_wrap_params = line_wrap_parameters(input_len, line_len, line_ending_len);
assert!(
buf.len() >= line_wrap_params.total_len,
"Buffer must be able to hold encoded data after line wrapping"
);
let last_line_start = input_len
.checked_sub(line_wrap_params.last_line_len)
.expect("Last line start index underflow");
let new_line_start = line_wrap_params.total_full_wrapped_lines_len;
buf.copy_within(
last_line_start..(last_line_start + line_wrap_params.last_line_len),
new_line_start,
);
let mut total_line_ending_bytes = 0;
let mut old_line_start = last_line_start;
let mut new_line_start = line_wrap_params.total_full_wrapped_lines_len;
for _ in 0..line_wrap_params.lines_with_endings {
let end_of_line_ending = new_line_start;
let start_of_line_ending = end_of_line_ending
.checked_sub(line_ending_len.get())
.expect("Line ending start index underflow");
old_line_start = old_line_start
.checked_sub(line_len)
.expect("Old line start index underflow");
new_line_start = new_line_start
.checked_sub(line_wrap_params.line_with_ending_len)
.expect("New line start index underflow");
buf.copy_within(old_line_start..old_line_start + line_len, new_line_start);
line_ending.write_ending(&mut buf[start_of_line_ending..end_of_line_ending]);
total_line_ending_bytes += line_ending_len.get();
}
assert_eq!(
line_wrap_params.total_line_endings_len,
total_line_ending_bytes
);
total_line_ending_bytes
}
#[derive(Debug, PartialEq)]
struct LineWrapParameters {
line_with_ending_len: usize,
lines_with_endings: usize,
last_line_len: usize,
total_full_wrapped_lines_len: usize,
total_len: usize,
total_line_endings_len: usize,
}
#[inline]
fn line_wrap_parameters(
input_len: usize,
line_len: usize,
line_ending_len: NonZeroUsize,
) -> LineWrapParameters {
let line_with_ending_len = line_len
.checked_add(line_ending_len.get())
.expect("Line length with ending exceeds usize");
if input_len <= line_len {
return LineWrapParameters {
line_with_ending_len,
lines_with_endings: 0,
last_line_len: input_len,
total_full_wrapped_lines_len: 0,
total_len: input_len,
total_line_endings_len: 0,
};
};
let (lines_with_endings, last_line_len) = if input_len % line_len > 0 {
(input_len / line_len, input_len % line_len)
} else {
(input_len / line_len - 1, line_len)
};
let total_full_wrapped_lines_len = lines_with_endings
.checked_mul(line_with_ending_len)
.expect("Full lines with endings length exceeds usize");
let total_len = total_full_wrapped_lines_len
.checked_add(last_line_len)
.expect("All lines with endings length exceeds usize");
let total_line_endings_len = lines_with_endings
.checked_mul(line_ending_len.get())
.expect("Total line endings length exceeds usize");
LineWrapParameters {
line_with_ending_len,
lines_with_endings,
last_line_len,
total_full_wrapped_lines_len,
total_len,
total_line_endings_len,
}
}
#[cfg(test)]
mod tests;