#![feature(let_chains)]
#![feature(type_alias_impl_trait)]
use derive_new::new as New;
use std::fmt::Display;
#[macro_use]
extern crate derive_builder;
#[derive(Clone, Debug, Builder)]
#[builder(derive(Debug))]
pub struct InFormat {
#[builder(default = "ItemSeparator::default()")]
pub item_separator: ItemSeparator,
#[builder(default = "None")]
pub line_separator: Option<LineSeparator>,
}
#[derive(Clone, Debug, Builder)]
#[builder(derive(Debug))]
pub struct OutFormat {
#[builder(default = "None")]
pub span: Option<ItemSpan>,
#[builder(default = "String::from(\" \")")]
pub item_separator: String,
#[builder(default = "None")]
pub line_separator: Option<LineSeparator>,
}
#[derive(New, Clone, Copy, Debug, PartialEq, Eq)]
pub struct ItemSpan {
span: usize,
pad: char,
anchor: Anchor,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Anchor {
Right,
Left,
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum ItemSeparator {
Explicit(String),
ByteCount(usize),
}
#[derive(New, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct LineSeparator {
items_per_line: usize,
line_separator: String,
}
impl Default for ItemSeparator {
fn default() -> Self {
Self::Explicit(",".to_string())
}
}
impl Display for ItemSeparator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
pub fn write<'i, In, Out>(
istream: In,
mut ostream: Out,
format: OutFormat,
) -> Result<(), std::io::Error>
where
In: Iterator<Item = &'i str>,
Out: std::io::Write,
{
let mut writer = ItemWriter::new(format);
for item in istream {
writer.write(item, &mut ostream)?;
}
Ok(())
}
pub type ItemIterator<'i> = impl Iterator<Item = &'i str> + std::fmt::Debug;
pub fn read(input: &str, format: InFormat) -> ItemIterator {
ItemReader::new(input, format)
}
enum EmittingSeparator {
None,
Item,
Line,
}
#[derive(New)]
pub struct ItemWriter {
#[new(value = "EmittingSeparator::None")]
separator: EmittingSeparator,
fmt: OutFormat,
#[new(value = "0")]
items_in_line: usize,
}
#[derive(New, Debug)]
pub struct ItemReader<'i> {
input: &'i str,
fmt: InFormat,
#[new(value = "0")]
items_in_current_line: usize,
}
impl<'i> ItemReader<'i> {
pub fn next_item(&mut self, separator: ItemSeparator) -> Option<&'i str> {
if self.input.is_empty() {
None
} else {
match &separator {
ItemSeparator::Explicit(separator) => match self.input.split_once(separator) {
None => {
let last = self.input;
self.input = "";
Some(last)
}
Some((item, remainder)) => {
self.input = remainder;
if item.is_empty() {
None
} else {
Some(item)
}
}
},
ItemSeparator::ByteCount(count) => {
if self.input.len() >= *count {
let split = self.input.split_at(*count);
self.input = split.1;
Some(split.0)
} else {
self.input = "";
None
}
}
}
}
}
}
impl<'i> Iterator for ItemReader<'i> {
type Item = &'i str;
fn next(&mut self) -> Option<Self::Item> {
let separator = {
if let Some(line_separator) = &self.fmt.line_separator {
if self.items_in_current_line == line_separator.items_per_line - 1 {
self.items_in_current_line = 0;
ItemSeparator::Explicit(line_separator.line_separator.clone())
} else {
self.items_in_current_line += 1;
self.fmt.item_separator.clone()
}
} else {
self.fmt.item_separator.clone()
}
};
self.next_item(separator)
}
}
impl ItemWriter {
pub fn write<Out: std::io::Write>(
&mut self,
item: &str,
writer: &mut Out,
) -> Result<(), std::io::Error> {
match self.separator {
EmittingSeparator::None => {}
EmittingSeparator::Item => {
writer.write_all(self.fmt.item_separator.as_bytes())?;
}
EmittingSeparator::Line => {
writer.write_all(
self.fmt
.line_separator
.as_ref()
.unwrap()
.line_separator
.as_bytes(),
)?;
}
}
let input_chars = item.chars().count();
if let Some(span) = self.fmt.span.as_ref() && input_chars < span.span {
let pad_count = span.span - input_chars;
let pad = String::from_iter(std::iter::repeat(span.pad).take(pad_count));
match span.anchor {
Anchor::Left => {
writer.write_all(item.as_bytes())?;
writer.write_all(pad.as_bytes())?;
}
Anchor::Right => {
writer.write_all(pad.as_bytes())?;
writer.write_all(item.as_bytes())?;
}
};
} else {
writer.write_all(item.as_bytes())?;
}
(self.separator, self.items_in_line) =
if let Some(line_separator) = self.fmt.line_separator.as_ref() {
if self.items_in_line + 1 < line_separator.items_per_line {
(EmittingSeparator::Item, self.items_in_line + 1)
} else {
(EmittingSeparator::Line, 0)
}
} else {
(EmittingSeparator::Item, 0)
};
Ok(())
}
}
#[cfg(test)]
mod write_test {
use super::*;
#[test]
fn test() {
let input = ["001", "01", "1"];
let expected = "_001|__01;___1*";
let mut output = [0u8; 15];
output[14] = b'*';
let format = OutFormatBuilder::default()
.span(Some(ItemSpan::new(4, '_', Anchor::Right)))
.item_separator("|".to_string())
.line_separator(Some(LineSeparator::new(2, ";".to_string())))
.build()
.unwrap();
write(input.into_iter(), output.as_mut_slice(), format).unwrap();
assert_eq!(String::from_utf8(output.to_vec()).unwrap(), expected);
}
#[test]
fn example() {
let input = ["ππ", "πΆ", "πΌπΌπΌ"];
let format = OutFormatBuilder::default()
.span(Some(ItemSpan::new(4, 'π', Anchor::Right)))
.item_separator("π".to_string())
.line_separator(Some(LineSeparator::new(2, "π©\n".to_string())))
.build()
.unwrap();
let expected = "πππππππππΆπ©\nππΌπΌπΌ";
let mut output = vec![0u8; 100];
write(input.into_iter(), output.as_mut_slice(), format).unwrap();
let eof = output
.iter()
.position(|x| *x == 0u8)
.unwrap_or(output.len());
let output = output.split_at(eof).0;
assert_eq!(String::from_utf8(output.to_vec()).unwrap(), expected);
}
}
#[cfg(test)]
mod read_test {
use super::*;
#[test]
fn reader_explicit() {
let input = "a,bb,ccc,,";
let mut reader = ItemReader::new(
input,
InFormatBuilder::default()
.item_separator(ItemSeparator::Explicit(",".to_string()))
.build()
.unwrap(),
);
assert_eq!(Some("a"), reader.next());
assert_eq!(Some("bb"), reader.next());
assert_eq!(Some("ccc"), reader.next());
assert_eq!(None, reader.next());
}
#[test]
fn reader_byte_count() {
let input = "aaaabbbbccccddd";
let mut reader = ItemReader::new(
input,
InFormatBuilder::default()
.item_separator(ItemSeparator::ByteCount(4))
.build()
.unwrap(),
);
assert_eq!(Some("aaaa"), reader.next());
assert_eq!(Some("bbbb"), reader.next());
assert_eq!(Some("cccc"), reader.next());
assert_eq!(None, reader.next());
}
#[test]
fn reader_explicit_multiline() {
let input = "aa,vvv,cccc,\nd,ee\n,a\n";
let mut reader = ItemReader::new(
input,
InFormatBuilder::default()
.item_separator(ItemSeparator::Explicit(",".to_string()))
.line_separator(Some(LineSeparator {
items_per_line: 3,
line_separator: "\n".to_string(),
}))
.build()
.unwrap(),
);
assert_eq!(Some("aa"), reader.next());
assert_eq!(Some("vvv"), reader.next());
assert_eq!(Some("cccc,"), reader.next());
assert_eq!(Some("d"), reader.next());
assert_eq!(Some("ee\n"), reader.next());
assert_eq!(Some("a"), reader.next());
assert_eq!(None, reader.next());
}
#[test]
fn reader_byte_count_multiline() {
let input = "aavvcc;ddeebb;";
let mut reader = ItemReader::new(
input,
InFormatBuilder::default()
.item_separator(ItemSeparator::ByteCount(2))
.line_separator(Some(LineSeparator {
items_per_line: 3,
line_separator: ";".to_string(),
}))
.build()
.unwrap(),
);
assert_eq!(Some("aa"), reader.next());
assert_eq!(Some("vv"), reader.next());
assert_eq!(Some("cc"), reader.next());
assert_eq!(Some("dd"), reader.next());
assert_eq!(Some("ee"), reader.next());
assert_eq!(Some("bb"), reader.next());
assert_eq!(None, reader.next());
}
#[test]
fn example_byte_count() {
let input = "aabbccdd";
let fmt = InFormatBuilder::default()
.item_separator(ItemSeparator::ByteCount(2))
.build()
.unwrap();
let mut it = read(input, fmt);
assert_eq!(Some("aa"), it.next());
assert_eq!(Some("bb"), it.next());
assert_eq!(Some("cc"), it.next());
assert_eq!(Some("dd"), it.next());
assert_eq!(None, it.next());
}
#[test]
fn example_explicit() {
let input = "πππSEPππSEPπSEPπΌπΌπΌ";
let fmt = InFormatBuilder::default()
.item_separator(ItemSeparator::Explicit("SEP".to_string()))
.build()
.unwrap();
let mut it = read(input, fmt);
assert_eq!(Some("πππ"), it.next());
assert_eq!(Some("ππ"), it.next());
assert_eq!(Some("π"), it.next());
assert_eq!(Some("πΌπΌπΌ"), it.next());
assert_eq!(None, it.next());
}
}