use rand::{Rng, SeedableRng};
use crate::text::ascii;
pub use eml_codec_derives::ToStringFromPrint;
use rand_chacha::ChaCha20Rng as RNG;
pub trait Print {
fn print(&self, fmt: &mut impl Formatter);
}
pub fn print_seq<T, Fmt>(fmt: &mut Fmt, s: &[T], sep: impl Fn(&mut Fmt))
where
T: Print,
Fmt: Formatter,
{
if !s.is_empty() {
s[0].print(fmt);
for x in &s[1..] {
sep(fmt);
x.print(fmt);
}
}
}
impl<T: Print> Print for &T {
fn print(&self, fmt: &mut impl Formatter) {
(*self).print(fmt)
}
}
pub trait Formatter {
fn begin_line_folding(&mut self);
fn end_line_folding(&mut self);
fn push_new_boundary(&mut self);
fn write_current_boundary(&mut self);
fn pop_boundary(&mut self);
fn write_bytes(&mut self, buf: &[u8]);
fn write_fws_bytes(&mut self, buf: &[u8]);
fn write_crlf(&mut self);
fn write_fws(&mut self) {
self.write_fws_bytes(b" ")
}
fn flush(self) -> Vec<u8>;
}
enum FormatterMode {
Direct,
Folding(LineFolder),
}
pub struct Fmt {
line_limit: Option<usize>,
mode: FormatterMode,
boundaries: Boundaries,
buf: Vec<u8>,
}
pub struct FmtConfig {
seed: Option<u64>,
line_limit: Option<usize>,
}
pub const FMT_DEFAULT: FmtConfig = FmtConfig {
seed: None,
line_limit: Some(78), };
pub const FMT_NOFOLD: FmtConfig = FMT_DEFAULT.with_line_limit(None);
impl FmtConfig {
pub const fn with_seed(self, seed: Option<u64>) -> Self {
Self { seed, ..self }
}
pub const fn with_line_limit(self, line_limit: Option<usize>) -> Self {
Self { line_limit, ..self }
}
}
impl Default for FmtConfig {
fn default() -> Self {
Self {
seed: None, line_limit: Some(78), }
}
}
impl Fmt {
pub fn new(cfg: FmtConfig) -> Self {
let rand = cfg
.seed
.map(RNG::seed_from_u64)
.unwrap_or_else(RNG::from_os_rng);
Self {
line_limit: cfg.line_limit,
mode: FormatterMode::Direct,
boundaries: Boundaries::new(rand),
buf: Vec::new(),
}
}
}
impl Formatter for Fmt {
fn begin_line_folding(&mut self) {
match self.mode {
FormatterMode::Direct => {
self.mode = FormatterMode::Folding(LineFolder::new(self.line_limit))
}
FormatterMode::Folding(_) => {
panic!("Formatter::begin_line_folding: already in folding mode")
}
}
}
fn end_line_folding(&mut self) {
match self.mode {
FormatterMode::Folding(ref mut folder) => {
folder.flush(&mut self.buf);
self.mode = FormatterMode::Direct
}
FormatterMode::Direct => {
panic!("Formatter::end_line_folding: not in folding mode")
}
}
}
fn push_new_boundary(&mut self) {
self.boundaries.push_new_boundary()
}
fn write_current_boundary(&mut self) {
let b = self.boundaries.current_boundary();
match self.mode {
FormatterMode::Direct => self.buf.extend_from_slice(b),
FormatterMode::Folding(ref mut folder) => folder.write_bytes(b, &mut self.buf),
}
}
fn pop_boundary(&mut self) {
self.boundaries.pop_boundary()
}
fn write_bytes(&mut self, buf: &[u8]) {
match self.mode {
FormatterMode::Direct => self.buf.extend_from_slice(buf),
FormatterMode::Folding(ref mut folder) => folder.write_bytes(buf, &mut self.buf),
}
}
fn write_fws_bytes(&mut self, buf: &[u8]) {
match self.mode {
FormatterMode::Direct => self.buf.extend_from_slice(buf),
FormatterMode::Folding(ref mut folder) => folder.write_fws_bytes(buf, &mut self.buf),
}
}
fn write_crlf(&mut self) {
match self.mode {
FormatterMode::Direct => self.buf.extend_from_slice(ascii::CRLF),
FormatterMode::Folding(ref mut folder) => folder.write_crlf(&mut self.buf),
}
}
fn flush(mut self) -> Vec<u8> {
self.boundaries.assert_empty();
if let FormatterMode::Folding(mut folder) = self.mode {
folder.flush(&mut self.buf)
}
self.buf
}
}
struct LineFolder {
line_limit: LineLimit,
prev_fold: Option<Vec<u8>>,
cur_fold: Vec<u8>,
cur_fold_is_only_fws: bool,
last_cut_candidate: Option<usize>,
is_flushed: bool,
}
impl LineFolder {
fn new(line_limit: Option<usize>) -> Self {
Self {
line_limit: LineLimit::from(line_limit),
prev_fold: None,
cur_fold: Vec::new(),
cur_fold_is_only_fws: true,
last_cut_candidate: None,
is_flushed: false,
}
}
fn flush(&mut self, inner: &mut Vec<u8>) {
if self.is_flushed {
return;
}
self.is_flushed = true;
self.flush_line(inner)
}
fn write_bytes(&mut self, buf: &[u8], inner: &mut Vec<u8>) {
assert!(!self.is_flushed);
if self.cur_fold.is_empty() && !buf.is_empty() {
assert!(!ascii::WS.contains(&buf[0]))
}
if self.cur_fold.len() + buf.len() <= self.line_limit || self.last_cut_candidate.is_none() {
self.cur_fold.extend_from_slice(buf);
if !buf.is_empty() {
self.cur_fold_is_only_fws = false;
}
} else {
self.fold(inner);
self.write_bytes(buf, inner)
}
}
fn write_fws_bytes(&mut self, buf: &[u8], inner: &mut Vec<u8>) {
assert!(!self.is_flushed);
if buf.is_empty() {
return;
}
assert!(!self.cur_fold.is_empty());
if !self.cur_fold_is_only_fws {
self.last_cut_candidate = Some(self.cur_fold.len());
}
self.cur_fold.push(buf[0]);
if self.cur_fold.len() > self.line_limit && self.last_cut_candidate.is_some() {
self.fold(inner)
}
self.write_fws_bytes(&buf[1..], inner)
}
fn write_crlf(&mut self, inner: &mut Vec<u8>) {
assert!(!self.is_flushed);
self.flush_line(inner);
inner.extend_from_slice(ascii::CRLF)
}
fn fold(&mut self, inner: &mut Vec<u8>) {
if let Some(prev_fold) = &self.prev_fold {
inner.extend_from_slice(prev_fold);
inner.extend_from_slice(ascii::CRLF);
self.prev_fold = None;
}
let cut_pos = self.last_cut_candidate.unwrap();
{
let mut prev_fold = self.cur_fold.split_off(cut_pos);
std::mem::swap(&mut self.cur_fold, &mut prev_fold);
self.prev_fold = Some(prev_fold);
}
self.last_cut_candidate = None;
self.cur_fold_is_only_fws = self.cur_fold.len() == 1
}
fn flush_line(&mut self, inner: &mut Vec<u8>) {
if let Some(prev_fold) = &self.prev_fold {
inner.extend_from_slice(prev_fold);
if self.cur_fold_is_only_fws {
} else {
inner.extend_from_slice(ascii::CRLF);
}
}
inner.extend_from_slice(&self.cur_fold);
self.prev_fold = None;
self.cur_fold.truncate(0);
self.cur_fold_is_only_fws = true;
self.last_cut_candidate = None
}
}
enum LineLimit {
NoLimit,
Limit(usize),
}
impl std::cmp::PartialEq<LineLimit> for usize {
fn eq(&self, limit: &LineLimit) -> bool {
match limit {
LineLimit::Limit(m) => self == m,
LineLimit::NoLimit => false,
}
}
}
impl std::cmp::PartialOrd<LineLimit> for usize {
fn partial_cmp(&self, limit: &LineLimit) -> Option<std::cmp::Ordering> {
match limit {
LineLimit::Limit(m) => self.partial_cmp(m),
LineLimit::NoLimit => Some(std::cmp::Ordering::Less),
}
}
}
impl From<Option<usize>> for LineLimit {
fn from(o: Option<usize>) -> Self {
match o {
None => Self::NoLimit,
Some(n) => Self::Limit(n),
}
}
}
struct Boundaries {
active_boundaries: Vec<Vec<u8>>, rand: RNG,
}
const BOUNDARY_LEN: usize = 65;
impl Boundaries {
fn new(rand: RNG) -> Self {
Self {
active_boundaries: Vec::new(),
rand,
}
}
fn push_new_boundary(&mut self) {
let b = self.random_boundary();
self.active_boundaries.push(b);
}
fn current_boundary(&self) -> &[u8] {
self.active_boundaries.last().unwrap()
}
fn pop_boundary(&mut self) {
self.active_boundaries.pop();
}
fn random_boundary(&mut self) -> Vec<u8> {
let mut v = Vec::with_capacity(BOUNDARY_LEN);
for _ in 0..BOUNDARY_LEN {
let n = self.rand.random_range(0..(10 + 26 + 26));
let byte = if n < 10 {
ascii::N0 + n
} else if n - 10 < 26 {
ascii::LCA + (n - 10)
} else {
ascii::LSA + (n - 10 - 26)
};
v.push(byte)
}
v
}
fn assert_empty(&self) {
assert!(self.active_boundaries.is_empty());
}
}
pub fn print_to_vec_with<F>(cfg: FmtConfig, f: F) -> Vec<u8>
where
F: for<'a> Fn(&'a mut Fmt),
{
let mut fmt = Fmt::new(cfg);
f(&mut fmt);
fmt.flush()
}
pub fn print_to_vec<T: Print>(cfg: FmtConfig, x: T) -> Vec<u8> {
print_to_vec_with(cfg, |fmt| x.print(fmt))
}
impl<'a> Print for std::borrow::Cow<'a, [u8]> {
fn print(&self, fmt: &mut impl Formatter) {
fmt.write_bytes(self)
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub fn print_to_vec_with(f: impl Fn(&mut Fmt)) -> Vec<u8> {
let cfg = FmtConfig {
seed: Some(0),
..FMT_DEFAULT
};
super::print_to_vec_with(cfg, f)
}
pub fn print_to_vec<T: Print>(x: T) -> Vec<u8> {
let cfg = FmtConfig {
seed: Some(0),
..FMT_DEFAULT
};
super::print_to_vec(cfg, x)
}
#[test]
fn test_folding() {
let folded = print_to_vec_with(|f| {
f.begin_line_folding();
f.write_bytes(&[b'x'; 72]);
f.write_fws();
f.write_bytes(b"yyyyyyyyy");
});
assert_eq!(folded, [&[b'x'; 72][..], b"\r\n yyyyyyyyy",].concat());
let folded = print_to_vec_with(|f| {
f.begin_line_folding();
f.write_bytes(&[b'x'; 80]);
f.write_fws();
f.write_bytes(b"yyyyyyyyy");
});
assert_eq!(folded, [&[b'x'; 80][..], b"\r\n yyyyyyyyy",].concat());
let folded = print_to_vec_with(|f| {
f.begin_line_folding();
f.write_bytes(&[b'x'; 18]);
f.write_fws_bytes(&[b' '; 3]);
f.write_bytes(&[b'x'; 16]);
f.write_fws();
f.write_bytes(&[b'x'; 32]);
f.write_fws();
f.write_bytes(&[b'y'; 9]);
});
assert_eq!(
folded,
[
&[b'x'; 18][..],
&[b' '; 3][..],
&[b'x'; 16][..],
&b" "[..],
&[b'x'; 32][..],
&b"\r\n "[..],
&[b'y'; 9][..],
]
.concat()
);
let folded = print_to_vec_with(|f| {
f.begin_line_folding();
f.write_bytes(b"X");
f.write_fws_bytes(&[b' '; 82]);
});
assert_eq!(folded, [&b"X"[..], &[b' '; 82],].concat());
}
}