use futures::{Stream, StreamExt, future, ready, stream};
use multipart_write::stream::MultipartStreamExt as _;
use multipart_write::{
BoxFusedMultipartWrite, BoxMultipartWrite, FusedMultipartWrite,
MultipartWrite, MultipartWriteExt as _,
};
use std::collections::BTreeMap;
use std::fmt::{self, Display, Formatter};
use std::pin::Pin;
use std::task::{Context, Poll};
#[derive(Debug, Clone, Copy)]
struct Narrative;
impl Narrative {
fn stream(lines: usize) -> impl Stream<Item = Line> {
stream::iter(Self).map(Line).take(lines)
}
}
impl Iterator for Narrative {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
Some("All work and no play make Jack a dull boy.".into())
}
}
#[derive(Debug, Clone)]
struct Line(String);
impl Display for Line {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone, Default)]
struct Page(Vec<Line>);
impl Page {
fn new(limit: usize) -> Self {
Self(Vec::with_capacity(limit))
}
fn line_count(&self) -> usize {
self.0.len()
}
fn write_line(&mut self, line_no: usize, line: Line) -> usize {
let Line(text) = line;
let with_ln = format!("{line_no:03} | {text}");
self.0.push(Line(with_ln));
self.0.len()
}
fn sort(&mut self) {
self.0.sort_by(|p1, p2| p1.0.cmp(&p2.0));
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd)]
struct PageNumber(usize);
impl Default for PageNumber {
fn default() -> Self {
Self(1)
}
}
impl PageNumber {
fn new_page(&mut self) {
self.0 += 1;
}
}
#[derive(Debug, Clone)]
struct Book(usize, BTreeMap<PageNumber, Page>);
impl Default for Book {
fn default() -> Self {
Self(1, BTreeMap::default())
}
}
impl Book {
fn edition(&self) -> String {
format!("Ed. {}", self.0)
}
fn start_next(&self) -> Book {
Book(self.0 + 1, BTreeMap::default())
}
fn write_page(
&mut self,
page_number: PageNumber,
mut page: Page,
) -> Result<(), String> {
if self.1.contains_key(&page_number) {
return Err(format!("page {} already exists", page_number.0));
}
page.sort();
self.1.insert(page_number, page);
Ok(())
}
fn iter_mut(&mut self) -> impl Iterator<Item = (&PageNumber, &mut Page)> {
self.1.iter_mut()
}
}
impl Display for Book {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let edition = self.edition();
if self.1.is_empty() {
return Ok(());
}
self.1.iter().try_for_each(|(page_number, page)| {
let mut len = 0;
let pg = format!("[{}]", page_number.0);
writeln!(f, "The Book, {edition}")?;
for line in &page.0 {
writeln!(f, "{line}")?;
len = line.0.len() + 5;
}
writeln!(f, "{pg:^len$} ")?;
Ok(())
})
}
}
struct Author<L> {
book: Book,
page: Page,
line_limit: usize,
current_page: PageNumber,
current_line: usize,
_line: std::marker::PhantomData<L>,
}
impl Default for Author<Line> {
fn default() -> Self {
Self::new(10)
}
}
impl Author<Line> {
fn new(line_limit: usize) -> Self {
Self {
book: Book::default(),
page: Page::new(line_limit),
line_limit,
current_page: PageNumber::default(),
current_line: 1,
_line: std::marker::PhantomData,
}
}
fn new_page(&mut self) -> Result<(), String> {
if self.page.line_count() == 0 {
return Ok(());
}
let finished_page =
std::mem::replace(&mut self.page, Page::new(self.line_limit));
self.book.write_page(self.current_page, finished_page)?;
self.current_page.new_page();
self.current_line = 1;
Ok(())
}
fn book_state(&self, lines_written: usize) -> BookState {
BookState { page_number: self.current_page, lines_written }
}
fn reverse(self) -> BoxMultipartWrite<'static, Line, (), Book, String> {
self.ready_part(|line: Line| async move {
let rev = line.0.chars().rev().collect();
Ok(Line(rev))
})
.boxed()
}
fn into_french(
self,
) -> BoxFusedMultipartWrite<'static, Line, BookState, Book, String> {
self.then(Translator::get_translation).box_fused()
}
}
#[derive(Debug, Clone, Copy)]
struct BookState {
page_number: PageNumber,
lines_written: usize,
}
impl BookState {
fn page_number(&self) -> usize {
self.page_number.0
}
}
impl Display for BookState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"current page: {}, total lines: {}",
self.page_number.0, self.lines_written
)
}
}
impl FusedMultipartWrite<Line> for Author<Line> {
fn is_terminated(&self) -> bool {
false
}
}
impl MultipartWrite<Line> for Author<Line> {
type Error = String;
type Output = Book;
type Recv = BookState;
fn poll_ready(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
if self.page.line_count() >= self.line_limit {
ready!(self.poll_flush(cx))?;
}
Poll::Ready(Ok(()))
}
fn start_send(
mut self: Pin<&mut Self>,
line: Line,
) -> Result<Self::Recv, Self::Error> {
let line_no = self.current_line;
let lines_written = self.page.write_line(line_no, line);
self.current_line += 1;
Ok(self.book_state(lines_written))
}
fn poll_flush(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
Poll::Ready(self.new_page())
}
fn poll_complete(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<Self::Output, Self::Error>> {
if self.page.line_count() > 0 {
ready!(self.as_mut().poll_flush(cx))?;
}
let new_book = self.book.start_next();
let book = std::mem::replace(&mut self.book, new_book);
self.current_page = PageNumber::default();
self.current_line = 1;
Poll::Ready(Ok(book))
}
}
const LINE_FR: &str =
"Tout le travail et aucun jeu font de Jack un garçon ennuyeux.";
#[derive(Debug, Clone, Copy, Default)]
struct Translator;
impl Translator {
async fn get_translation(
res: Result<Book, String>,
) -> Result<Book, String> {
let mut book = res?;
book.iter_mut().for_each(|(_, pg)| Self::translate_page(pg));
Ok(book)
}
fn translate_page(pg: &mut Page) {
let new_lines =
std::iter::repeat_n(LINE_FR.to_string(), pg.line_count())
.enumerate()
.map(|(n, txt)| Line(format!("{n:03} | {txt}")))
.collect();
pg.0 = new_lines;
}
}
const LIMIT: usize = 625;
#[tokio::main]
async fn main() -> Result<(), String> {
let short_story = Narrative::stream(LIMIT)
.complete_with(Author::default())
.await
.unwrap();
println!("{short_story}");
println!("=========================");
let short_story_reversed = Narrative::stream(LIMIT)
.complete_with(Author::default().reverse())
.await
.unwrap();
println!("{short_story_reversed}");
println!("=========================");
let books: Vec<Book> = Narrative::stream(LIMIT)
.try_complete_when(Author::default(), |b| b.page_number() >= 25)
.filter_map(|res| future::ready(res.ok()))
.collect()
.await;
books.into_iter().for_each(|book| println!("{book}"));
println!("=========================");
let french_books: Vec<Book> = Narrative::stream(LIMIT)
.try_complete_when(Author::default().into_french(), |b| {
b.page_number() >= 25
})
.filter_map(|res| future::ready(res.ok()))
.collect()
.await;
french_books.into_iter().for_each(|book| println!("{book}"));
println!("=========================");
Ok(())
}