1use std::{error::Error, fmt};
2
3use log::error;
4
5use crate::text::{CoverTextLineIterator, CoverTextWordIterator};
6
7pub trait Context {
9 fn get_current_text_mut(&mut self) -> Result<&mut String, ContextError>;
14
15 fn get_current_text(&self) -> Result<&String, ContextError>;
20
21 fn load_text(&mut self) -> Result<&str, ContextError>;
26}
27
28pub struct PivotByRawLineContext {
31 pivot: usize,
32 cover_text_iter: CoverTextLineIterator,
33 current_text: Option<String>,
34}
35pub struct PivotByLineContext {
39 pivot: usize,
40 cover_text_iter: CoverTextWordIterator,
41 current_text: Option<String>,
42}
43
44impl PivotByRawLineContext {
45 pub fn new(cover_text: &str, pivot: usize) -> Self {
46 PivotByRawLineContext {
47 pivot,
48 cover_text_iter: CoverTextLineIterator::new(cover_text),
49 current_text: None,
50 }
51 }
52
53 pub fn get_pivot(&self) -> usize {
54 self.pivot
55 }
56}
57
58impl PivotByLineContext {
59 const WORD_DELIMITER: char = ' ';
60
61 pub fn new(cover_text: &str, pivot: usize) -> Self {
62 PivotByLineContext {
63 pivot,
64 cover_text_iter: CoverTextWordIterator::new(cover_text),
65 current_text: None,
66 }
67 }
68
69 pub fn get_pivot(&self) -> usize {
70 self.pivot
71 }
72
73 pub fn peek_word(&mut self) -> Option<String> {
78 self.cover_text_iter.peek()
79 }
80
81 pub fn next_word(&mut self) -> Option<String> {
86 self.cover_text_iter.next()
87 }
88
89 fn construct_line_by_pivot(&mut self) -> Result<Option<String>, ContextError> {
95 let maybe_word = self.cover_text_iter.peek();
96
97 if maybe_word.is_none() {
98 return Ok(None);
99 }
100
101 let mut word = maybe_word.unwrap();
102
103 if word.len() > self.pivot {
104 error!("Stuck at word of length {}.", word.len());
105 return Err(ContextError { kind: ContextErrorKind::CannotConstructLine });
106 }
107 let mut line = String::new();
108 while line.len() + word.len() <= self.pivot {
109 line.push_str(&word);
110 line.push(Self::WORD_DELIMITER);
111 self.cover_text_iter.next();
113
114 if let Some(next_word) = self.cover_text_iter.peek() {
115 word = next_word;
116 } else {
117 return Ok(Some(line));
118 }
119 }
120 Ok(Some(line.trim_end().to_string()))
121 }
122}
123
124impl Context for PivotByLineContext {
125 fn get_current_text_mut(&mut self) -> Result<&mut String, ContextError> {
126 self.current_text
127 .as_mut()
128 .ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
129 }
130
131 fn get_current_text(&self) -> Result<&String, ContextError> {
132 self.current_text
133 .as_ref()
134 .ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
135 }
136
137 fn load_text(&mut self) -> Result<&str, ContextError> {
143 self.current_text = self.construct_line_by_pivot()?;
144 self.current_text
145 .as_deref()
146 .ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
147 }
148}
149
150impl Context for PivotByRawLineContext {
151 fn get_current_text_mut(&mut self) -> Result<&mut String, ContextError> {
152 self.current_text
153 .as_mut()
154 .ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
155 }
156
157 fn get_current_text(&self) -> Result<&String, ContextError> {
158 self.current_text
159 .as_ref()
160
161 .ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
162 }
163
164 fn load_text(&mut self) -> Result<&str, ContextError> {
169 self.current_text = self.cover_text_iter.next();
170 self.current_text
171 .as_deref()
172 .ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
173 }
174}
175
176#[derive(Debug, Clone, Copy)]
178pub enum ContextErrorKind {
179 NoTextLeft,
180 CannotConstructLine
181}
182
183#[derive(Debug)]
185pub struct ContextError {
186 kind: ContextErrorKind,
187}
188
189impl ContextError {
190 fn new(kind: ContextErrorKind) -> Self {
191 ContextError { kind }
192 }
193
194 pub fn kind(&self) -> ContextErrorKind {
195 self.kind
196 }
197}
198
199#[cfg(not(tarpaulin_include))]
200impl fmt::Display for ContextError {
201 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202 match self.kind {
203 ContextErrorKind::NoTextLeft => write!(f, "No cover text left.",),
204 ContextErrorKind::CannotConstructLine => write!(f, "Pivot is too small. Couldn't load the line no longer than the pivot.",),
205 }
206 }
207}
208
209impl Error for ContextError {}