1use std::{
11 fmt::{Alignment, Display, Write},
12 marker::PhantomData,
13 path::PathBuf,
14};
15
16use super::{Change, Tagger, Text};
17use crate::{
18 form::FormId,
19 text::{AlignCenter, AlignLeft, AlignRight, Ghost, Spacer},
20};
21
22#[derive(Clone)]
58pub struct Builder {
59 text: Text,
60 last_form: Option<(usize, FormId)>,
61 last_align: Option<(usize, Alignment)>,
62 buffer: String,
63 last_was_empty: bool,
64}
65
66impl Builder {
67 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn build(mut self) -> Text {
87 self.push_str("\n");
88 self.build_no_nl()
89 }
90
91 fn build_no_nl(mut self) -> Text {
101 if let Some((b, id)) = self.last_form
102 && b < self.text.len().byte()
103 {
104 self.text.insert_tag(Tagger::basic(), b.., id.to_tag(0));
105 }
106 if let Some((b, align)) = self.last_align
107 && b < self.text.len().byte()
108 {
109 match align {
110 Alignment::Center => self.text.insert_tag(Tagger::basic(), b.., AlignCenter),
111 Alignment::Right => self.text.insert_tag(Tagger::basic(), b.., AlignRight),
112 _ => {}
113 }
114 }
115
116 self.text
117 }
118
119 pub fn push<D: Display, _T>(&mut self, part: impl Into<BuilderPart<D, _T>>) {
127 fn push_basic(builder: &mut Builder, part: BuilderPart) {
128 use Alignment::*;
129 use BuilderPart as BP;
130
131 let end = builder.text.len().byte();
132 match part {
133 BP::Text(text) => builder.push_text(text),
134 BP::Form(id) => {
135 let last_form = if id == crate::form::DEFAULT_ID {
136 builder.last_form.take()
137 } else {
138 builder.last_form.replace((end, id))
139 };
140
141 if let Some((b, id)) = last_form
142 && b < end
143 {
144 builder.text.insert_tag(Tagger::basic(), b.., id.to_tag(0));
145 }
146 }
147 BP::AlignLeft => match builder.last_align.take() {
148 Some((b, Center)) if b < end => {
149 builder.text.insert_tag(Tagger::basic(), b.., AlignCenter);
150 }
151 Some((b, Right)) if b < end => {
152 builder.text.insert_tag(Tagger::basic(), b.., AlignRight);
153 }
154 _ => {}
155 },
156 BP::AlignCenter => match builder.last_align.take() {
157 Some((b, Center)) => builder.last_align = Some((b, Center)),
158 Some((b, Right)) if b < end => {
159 builder.text.insert_tag(Tagger::basic(), b.., AlignRight);
160 builder.last_align = Some((end, Center));
161 }
162 None => builder.last_align = Some((end, Center)),
163 Some(_) => {}
164 },
165 BP::AlignRight => match builder.last_align.take() {
166 Some((b, Right)) => builder.last_align = Some((b, Right)),
167 Some((b, Center)) if b < end => {
168 builder.text.insert_tag(Tagger::basic(), b.., AlignCenter);
169 builder.last_align = Some((end, Right));
170 }
171 None => builder.last_align = Some((end, Right)),
172 Some(_) => {}
173 },
174 BP::Spacer(_) => builder.text.insert_tag(Tagger::basic(), end, Spacer),
175 BP::Ghost(text) => builder.text.insert_tag(Tagger::basic(), end, Ghost(text)),
176 BP::ToString(_) => unsafe { std::hint::unreachable_unchecked() },
177 }
178 }
179
180 match Into::<BuilderPart<D, _T>>::into(part).try_to_basic() {
181 Ok(basic_part) => push_basic(self, basic_part),
182 Err(BuilderPart::ToString(display)) => self.push_str(display),
183 Err(_) => unsafe { std::hint::unreachable_unchecked() },
184 }
185 }
186
187 pub fn last_was_empty(&self) -> bool {
192 self.last_was_empty
193 }
194
195 pub fn push_str<D: Display>(&mut self, d: D) {
199 self.buffer.clear();
200 write!(self.buffer, "{d}").unwrap();
201 if self.buffer.is_empty() {
202 self.last_was_empty = true;
203 } else {
204 self.last_was_empty = false;
205 let end = self.text.len();
206 self.text
207 .apply_change_inner(0, Change::str_insert(&self.buffer, end));
208 }
209 }
210
211 fn push_text(&mut self, text: Text) {
213 self.last_was_empty = text.is_empty();
214
215 if let Some((b, id)) = self.last_form.take() {
216 self.text.insert_tag(Tagger::basic(), b.., id.to_tag(0));
217 }
218
219 self.text.0.bytes.extend(text.0.bytes);
220 self.text.0.tags.extend(text.0.tags);
221 }
222}
223
224impl Default for Builder {
225 fn default() -> Self {
226 Builder {
227 text: Text::empty(),
228 last_form: None,
229 last_align: None,
230 buffer: String::with_capacity(50),
231 last_was_empty: false,
232 }
233 }
234}
235
236impl std::fmt::Debug for Builder {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 f.debug_struct("Builder")
239 .field("text", &self.text)
240 .finish_non_exhaustive()
241 }
242}
243
244impl From<Builder> for Text {
245 fn from(value: Builder) -> Self {
246 value.build()
247 }
248}
249
250#[derive(Clone)]
252pub enum BuilderPart<D: Display = String, _T = ()> {
253 Text(Text),
255 ToString(D),
257 Form(FormId),
259 AlignLeft,
261 AlignCenter,
263 AlignRight,
265 Spacer(PhantomData<_T>),
267 Ghost(Text),
269}
270
271impl<D: Display, _T> BuilderPart<D, _T> {
272 fn try_to_basic(self) -> Result<BuilderPart, Self> {
273 match self {
274 BuilderPart::Text(text) => Ok(BuilderPart::Text(text)),
275 BuilderPart::ToString(d) => Err(BuilderPart::ToString(d)),
276 BuilderPart::Form(form_id) => Ok(BuilderPart::Form(form_id)),
277 BuilderPart::AlignLeft => Ok(BuilderPart::AlignLeft),
278 BuilderPart::AlignCenter => Ok(BuilderPart::AlignCenter),
279 BuilderPart::AlignRight => Ok(BuilderPart::AlignRight),
280 BuilderPart::Spacer(_) => Ok(BuilderPart::Spacer(PhantomData)),
281 BuilderPart::Ghost(text) => Ok(BuilderPart::Ghost(text)),
282 }
283 }
284}
285
286impl From<Builder> for BuilderPart {
287 fn from(value: Builder) -> Self {
288 Self::Text(value.build_no_nl())
289 }
290}
291
292impl From<FormId> for BuilderPart {
293 fn from(value: FormId) -> Self {
294 Self::Form(value)
295 }
296}
297
298impl From<AlignLeft> for BuilderPart {
299 fn from(_: AlignLeft) -> Self {
300 BuilderPart::AlignLeft
301 }
302}
303
304impl From<AlignCenter> for BuilderPart {
305 fn from(_: AlignCenter) -> Self {
306 BuilderPart::AlignCenter
307 }
308}
309
310impl From<AlignRight> for BuilderPart {
311 fn from(_: AlignRight) -> Self {
312 BuilderPart::AlignRight
313 }
314}
315
316impl From<Spacer> for BuilderPart {
317 fn from(_: Spacer) -> Self {
318 BuilderPart::Spacer(PhantomData)
319 }
320}
321
322impl<T: Into<Text>> From<Ghost<T>> for BuilderPart {
323 fn from(value: Ghost<T>) -> Self {
324 BuilderPart::Ghost(value.0.into())
325 }
326}
327
328impl From<Text> for BuilderPart {
329 fn from(value: Text) -> Self {
330 BuilderPart::Text(value.without_last_nl())
331 }
332}
333
334impl<D: Display> From<D> for BuilderPart<D, D> {
335 fn from(value: D) -> Self {
336 BuilderPart::ToString(value)
337 }
338}
339
340impl From<PathBuf> for BuilderPart {
341 fn from(value: PathBuf) -> Self {
342 BuilderPart::ToString(value.to_string_lossy().to_string())
343 }
344}
345
346impl From<&PathBuf> for BuilderPart {
347 fn from(value: &PathBuf) -> Self {
348 BuilderPart::ToString(value.to_string_lossy().to_string())
349 }
350}
351
352impl From<std::process::Output> for BuilderPart {
353 fn from(value: std::process::Output) -> Self {
354 BuilderPart::ToString(String::from_utf8_lossy(&value.stdout).into_owned())
355 }
356}
357
358pub macro txt($($parts:tt)+) {{
364 #[allow(unused_imports)]
365 use $crate::private_exports::{format_like, parse_arg, parse_form, parse_str};
366
367 let mut builder = $crate::text::Builder::new();
368
369 format_like!(
370 parse_str,
371 [('{', parse_arg, false), ('[', parse_form, true)],
372 &mut builder,
373 $($parts)*
374 );
375
376 builder
377}}