1use crate::{Coordinates, Input};
4use displaydoc::Display;
5use std::io::{BufReader, Error as IoError, Read};
6use thiserror::Error;
7
8#[derive(Debug, Error, Display)]
10pub enum Error {
11 UnexpectedChar(char),
13 IoError(#[from] IoError),
15}
16
17#[must_use]
52#[derive(Debug)]
53pub struct Plaintext<I: Input> {
54 lines: I::Lines,
56
57 current_line: Option<I::Bytes>,
59
60 position: Coordinates,
62}
63
64impl<I: Input> Plaintext<I> {
65 pub fn new(input: I) -> Result<Self, Error> {
67 let mut lines = input.lines();
68 let mut current_line = None;
69 for item in &mut lines {
70 let line = I::line(item)?;
71 if !line.as_ref().starts_with('!') {
72 current_line = Some(I::bytes(line));
73 break;
74 }
75 }
76 Ok(Self {
77 lines,
78 current_line,
79 position: (0, 0),
80 })
81 }
82}
83
84impl<I, L> Plaintext<I>
85where
86 I: Input<Lines = L>,
87 L: Input,
88{
89 pub fn remains(self) -> Result<Plaintext<L>, Error> {
91 Plaintext::new(self.lines)
92 }
93}
94
95impl<R: Read> Plaintext<BufReader<R>> {
96 pub fn new_from_file(file: R) -> Result<Self, Error> {
98 Self::new(BufReader::new(file))
99 }
100}
101
102impl<I: Input> Clone for Plaintext<I>
103where
104 I::Lines: Clone,
105 I::Bytes: Clone,
106{
107 fn clone(&self) -> Self {
108 Self {
109 lines: self.lines.clone(),
110 current_line: self.current_line.clone(),
111 position: self.position,
112 }
113 }
114}
115
116impl<I: Input> Iterator for Plaintext<I> {
118 type Item = Result<Coordinates, Error>;
119
120 fn next(&mut self) -> Option<Self::Item> {
121 loop {
122 if let Some(c) = self.current_line.as_mut().and_then(Iterator::next) {
123 match c {
124 b'O' | b'*' => {
125 let cell = self.position;
126 self.position.0 += 1;
127 return Some(Ok(cell));
128 }
129 b'.' => self.position.0 += 1,
130 _ if c.is_ascii_whitespace() => continue,
131 _ => return Some(Err(Error::UnexpectedChar(char::from(c)))),
132 }
133 } else if let Some(item) = self.lines.next() {
134 match I::line(item) {
135 Ok(line) => {
136 if line.as_ref().starts_with('!') {
137 continue;
138 } else {
139 self.position.0 = 0;
140 self.position.1 += 1;
141 self.current_line = Some(I::bytes(line));
142 }
143 }
144 Err(e) => {
145 return Some(Err(Error::IoError(e)));
146 }
147 }
148 } else {
149 return None;
150 }
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn plaintext_glider() -> Result<(), Error> {
161 const GLIDER: &str = r"!Name: Glider
162!
163.O.
164..O
165OOO";
166
167 let glider = Plaintext::new(GLIDER)?;
168
169 let _ = glider.clone();
170
171 let cells = glider.collect::<Result<Vec<_>, _>>()?;
172 assert_eq!(cells, vec![(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)]);
173 Ok(())
174 }
175}