lineup/
lib.rs

1#![feature(let_chains)]
2#![feature(type_alias_impl_trait)]
3
4use derive_new::new as New;
5use std::fmt::Display;
6
7#[macro_use]
8extern crate derive_builder;
9
10#[derive(Clone, Debug, Builder)]
11#[builder(derive(Debug))]
12pub struct InFormat {
13    #[builder(default = "ItemSeparator::default()")]
14    pub item_separator: ItemSeparator,
15
16    #[builder(default = "None")]
17    pub line_separator: Option<LineSeparator>,
18}
19
20#[derive(Clone, Debug, Builder)]
21#[builder(derive(Debug))]
22/// Output format
23///
24/// conveniently FormatBuilder struct can be used for construction:
25///
26/// # Examples
27///
28/// ```
29/// let format = lineup::OutFormatBuilder::default()
30///     .span(Some(lineup::ItemSpan::new(4, '_', lineup::Anchor::Right)))
31///     .item_separator("|".to_string())
32///     .line_separator(Some(lineup::LineSeparator::new(2, ";".to_string())))
33///     .build()
34///     .unwrap();
35/// ```
36///
37pub struct OutFormat {
38    #[builder(default = "None")]
39    /// Item Span (see [ItemSpan])
40    ///
41    /// [ItemSpan]: crate::ItemSpan
42    pub span: Option<ItemSpan>,
43
44    #[builder(default = "String::from(\" \")")]
45    /// Separator for items within a line
46    pub item_separator: String,
47
48    #[builder(default = "None")]
49    /// Separator for lines
50    pub line_separator: Option<LineSeparator>,
51}
52
53#[derive(New, Clone, Copy, Debug, PartialEq, Eq)]
54/// Output items span
55pub struct ItemSpan {
56    /// Max characters an item would need; shorter represantions would be padded with [pad]
57    /// and anchored as per [anchor];
58    ///
59    /// [pad]: crate::ItemSpan::pad
60    /// [anchor]: crate::ItemSpan::anchor
61    span: usize,
62
63    /// Pad character to use for items whose length is less than [span]
64    ///
65    /// [span]: crate::ItemSpan::span
66    pad: char,
67
68    /// Anchor type for items when padding is needed (see [span])
69    ///
70    /// [span]: crate::ItemSpan::span
71    anchor: Anchor,
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75/// Anchor type for items when padding is needed
76pub enum Anchor {
77    /// Anchor items to the right
78    Right,
79    /// Anchor items to the left
80    Left,
81}
82
83#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
84pub enum ItemSeparator {
85    /// explicit item separator
86    Explicit(String),
87    /// item fixed byte size, no explicit separator
88    ByteCount(usize),
89}
90
91#[derive(New, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
92pub struct LineSeparator {
93    items_per_line: usize,
94    line_separator: String,
95}
96
97impl Default for ItemSeparator {
98    fn default() -> Self {
99        Self::Explicit(",".to_string())
100    }
101}
102
103impl Display for ItemSeparator {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        write!(f, "{self:?}")
106    }
107}
108
109/// Write all input items as per provided format
110///
111/// # Examples
112///
113/// ```
114/// let input = ["😊😊", "πŸ‘Ά", "πŸ’ΌπŸ’ΌπŸ’Ό"];
115/// let format = lineup::OutFormatBuilder::default()
116///     .span(Some(lineup::ItemSpan::new(4, 'πŸ‘‰', lineup::Anchor::Right)))
117///     .item_separator("πŸ––".to_string())
118///     .line_separator(Some(lineup::LineSeparator::new(2, "πŸ”©\n".to_string())))
119///     .build()
120///     .unwrap();
121/// let expected = "πŸ‘‰πŸ‘‰πŸ˜ŠπŸ˜ŠπŸ––πŸ‘‰πŸ‘‰πŸ‘‰πŸ‘ΆπŸ”©\nπŸ‘‰πŸ’ΌπŸ’ΌπŸ’Ό";
122/// let mut output = vec![0u8; 100 ];
123/// lineup::write(input.into_iter(), output.as_mut_slice(), format).unwrap();
124/// let eof = output.iter().position(|x| *x == 0u8).unwrap_or(output.len());
125/// let output = output.split_at(eof).0;
126/// assert_eq!(String::from_utf8(output.to_vec()).unwrap(), expected);
127/// ```
128///
129pub fn write<'i, In, Out>(
130    istream: In,
131    mut ostream: Out,
132    format: OutFormat,
133) -> Result<(), std::io::Error>
134where
135    In: Iterator<Item = &'i str>,
136    Out: std::io::Write,
137{
138    let mut writer = ItemWriter::new(format);
139    for item in istream {
140        writer.write(item, &mut ostream)?;
141    }
142    Ok(())
143}
144
145/// Opaque type definition around [ItemReader], as returned from [read]
146///
147/// [ItemReader]: crate::ItemReader
148/// [read]: crate::read
149pub type ItemIterator<'i> = impl Iterator<Item = &'i str> + std::fmt::Debug;
150
151/// Get an iterator over &str items
152///
153/// # Examples
154///
155/// separate by byte count for each item
156/// ```
157/// let input = "aabbccdd";
158/// let fmt = lineup::InFormatBuilder::default()
159///     .item_separator(lineup::ItemSeparator::ByteCount(2))
160///     .build()
161///     .unwrap();
162/// let mut it = lineup::read(input, fmt);
163/// assert_eq!(Some("aa"), it.next());
164/// assert_eq!(Some("bb"), it.next());
165/// assert_eq!(Some("cc"), it.next());
166/// assert_eq!(Some("dd"), it.next());
167/// assert_eq!(None, it.next());
168/// ```
169///
170/// separate by explicit string
171/// ```
172/// let input = "πŸ‘‰πŸ‘‰πŸ‘‰SEP😊😊SEPπŸ––SEPπŸ’ΌπŸ’ΌπŸ’Ό";
173/// let fmt = lineup::InFormatBuilder::default()
174///     .item_separator(lineup::ItemSeparator::Explicit("SEP".to_string()))
175///     .build()
176///     .unwrap();
177/// let mut it = lineup::read(input, fmt);
178/// assert_eq!(Some("πŸ‘‰πŸ‘‰πŸ‘‰"), it.next());
179/// assert_eq!(Some("😊😊"), it.next());
180/// assert_eq!(Some("πŸ––"), it.next());
181/// assert_eq!(Some("πŸ’ΌπŸ’ΌπŸ’Ό"), it.next());
182/// assert_eq!(None, it.next());
183/// ```
184///
185pub fn read(input: &str, format: InFormat) -> ItemIterator {
186    ItemReader::new(input, format)
187}
188
189enum EmittingSeparator {
190    None,
191    Item,
192    Line,
193}
194
195/// Write input items as per provided format (see [write])
196///
197/// [write]: ItemWriter::write
198#[derive(New)]
199pub struct ItemWriter {
200    #[new(value = "EmittingSeparator::None")]
201    separator: EmittingSeparator,
202    fmt: OutFormat,
203    #[new(value = "0")]
204    items_in_line: usize,
205}
206
207#[derive(New, Debug)]
208pub struct ItemReader<'i> {
209    input: &'i str,
210    fmt: InFormat,
211    #[new(value = "0")]
212    items_in_current_line: usize,
213}
214
215impl<'i> ItemReader<'i> {
216    pub fn next_item(&mut self, separator: ItemSeparator) -> Option<&'i str> {
217        if self.input.is_empty() {
218            None
219        } else {
220            match &separator {
221                ItemSeparator::Explicit(separator) => match self.input.split_once(separator) {
222                    None => {
223                        let last = self.input;
224                        self.input = "";
225                        Some(last)
226                    }
227                    Some((item, remainder)) => {
228                        self.input = remainder;
229                        if item.is_empty() {
230                            None
231                        } else {
232                            Some(item)
233                        }
234                    }
235                },
236                ItemSeparator::ByteCount(count) => {
237                    if self.input.len() >= *count {
238                        let split = self.input.split_at(*count);
239                        self.input = split.1;
240                        Some(split.0)
241                    } else {
242                        self.input = "";
243                        None
244                    }
245                }
246            }
247        }
248    }
249}
250
251impl<'i> Iterator for ItemReader<'i> {
252    type Item = &'i str;
253    fn next(&mut self) -> Option<Self::Item> {
254        let separator = {
255            if let Some(line_separator) = &self.fmt.line_separator {
256                if self.items_in_current_line == line_separator.items_per_line - 1 {
257                    self.items_in_current_line = 0;
258                    ItemSeparator::Explicit(line_separator.line_separator.clone())
259                } else {
260                    self.items_in_current_line += 1;
261                    self.fmt.item_separator.clone()
262                }
263            } else {
264                self.fmt.item_separator.clone()
265            }
266        };
267        self.next_item(separator)
268    }
269}
270
271impl ItemWriter {
272    /// Write input item as per provided format
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// let input = ["😊😊", "πŸ‘Ά", "πŸ’ΌπŸ’ΌπŸ’Ό"];
278    /// let format = lineup::OutFormatBuilder::default()
279    ///     .span(Some(lineup::ItemSpan::new(4, 'πŸ‘‰', lineup::Anchor::Right)))
280    ///     .item_separator("πŸ––".to_string())
281    ///     .line_separator(Some(lineup::LineSeparator::new(2, "πŸ”©\n".to_string())))
282    ///     .build()
283    ///     .unwrap();
284    /// let expected = "πŸ‘‰πŸ‘‰πŸ˜ŠπŸ˜ŠπŸ––πŸ‘‰πŸ‘‰πŸ‘‰πŸ‘ΆπŸ”©\nπŸ‘‰πŸ’ΌπŸ’ΌπŸ’Ό";
285    /// let mut output = vec![0u8; 100 ];
286    /// let mut writer = lineup::ItemWriter::new(format);
287    /// let istream = input.into_iter();
288    /// let mut ostream = output.as_mut_slice();
289    /// for item in istream {
290    ///     writer.write(item, &mut ostream).unwrap();
291    /// }
292    /// let eof = output.iter().position(|x| *x == 0u8).unwrap_or(output.len());
293    /// let output = output.split_at(eof).0;
294    /// assert_eq!(String::from_utf8(output.to_vec()).unwrap(), expected);
295    /// ```
296    ///
297    pub fn write<Out: std::io::Write>(
298        &mut self,
299        item: &str,
300        writer: &mut Out,
301    ) -> Result<(), std::io::Error> {
302        // emit separator from previous input
303        match self.separator {
304            EmittingSeparator::None => {}
305            EmittingSeparator::Item => {
306                writer.write_all(self.fmt.item_separator.as_bytes())?;
307            }
308            EmittingSeparator::Line => {
309                writer.write_all(
310                    self.fmt
311                        .line_separator
312                        .as_ref()
313                        .unwrap()
314                        .line_separator
315                        .as_bytes(),
316                )?;
317            }
318        }
319
320        // write (padded) input
321        let input_chars = item.chars().count();
322        if let Some(span) = self.fmt.span.as_ref() && input_chars < span.span {
323            let pad_count = span.span - input_chars;
324            let pad = String::from_iter(std::iter::repeat(span.pad).take(pad_count));
325            match span.anchor {
326                Anchor::Left => {
327                    writer.write_all(item.as_bytes())?;
328                    writer.write_all(pad.as_bytes())?;
329                }
330                Anchor::Right => {
331                    writer.write_all(pad.as_bytes())?;
332                    writer.write_all(item.as_bytes())?;
333                }
334            };
335        } else {
336            writer.write_all(item.as_bytes())?;
337        }
338
339        // decide on separator for next input
340        (self.separator, self.items_in_line) =
341            if let Some(line_separator) = self.fmt.line_separator.as_ref() {
342                if self.items_in_line + 1 < line_separator.items_per_line {
343                    (EmittingSeparator::Item, self.items_in_line + 1)
344                } else {
345                    (EmittingSeparator::Line, 0)
346                }
347            } else {
348                (EmittingSeparator::Item, 0)
349            };
350        Ok(())
351    }
352}
353
354#[cfg(test)]
355mod write_test {
356    use super::*;
357
358    #[test]
359    fn test() {
360        let input = ["001", "01", "1"];
361        let expected = "_001|__01;___1*";
362        let mut output = [0u8; 15];
363        output[14] = b'*';
364        let format = OutFormatBuilder::default()
365            .span(Some(ItemSpan::new(4, '_', Anchor::Right)))
366            .item_separator("|".to_string())
367            .line_separator(Some(LineSeparator::new(2, ";".to_string())))
368            .build()
369            .unwrap();
370        write(input.into_iter(), output.as_mut_slice(), format).unwrap();
371        assert_eq!(String::from_utf8(output.to_vec()).unwrap(), expected);
372    }
373
374    #[test]
375    fn example() {
376        let input = ["😊😊", "πŸ‘Ά", "πŸ’ΌπŸ’ΌπŸ’Ό"];
377        let format = OutFormatBuilder::default()
378            .span(Some(ItemSpan::new(4, 'πŸ‘‰', Anchor::Right)))
379            .item_separator("πŸ––".to_string())
380            .line_separator(Some(LineSeparator::new(2, "πŸ”©\n".to_string())))
381            .build()
382            .unwrap();
383        let expected = "πŸ‘‰πŸ‘‰πŸ˜ŠπŸ˜ŠπŸ––πŸ‘‰πŸ‘‰πŸ‘‰πŸ‘ΆπŸ”©\nπŸ‘‰πŸ’ΌπŸ’ΌπŸ’Ό";
384        let mut output = vec![0u8; 100];
385        write(input.into_iter(), output.as_mut_slice(), format).unwrap();
386        let eof = output
387            .iter()
388            .position(|x| *x == 0u8)
389            .unwrap_or(output.len());
390        let output = output.split_at(eof).0;
391        assert_eq!(String::from_utf8(output.to_vec()).unwrap(), expected);
392    }
393}
394
395#[cfg(test)]
396mod read_test {
397    use super::*;
398
399    #[test]
400    fn reader_explicit() {
401        let input = "a,bb,ccc,,";
402        let mut reader = ItemReader::new(
403            input,
404            InFormatBuilder::default()
405                .item_separator(ItemSeparator::Explicit(",".to_string()))
406                .build()
407                .unwrap(),
408        );
409        assert_eq!(Some("a"), reader.next());
410        assert_eq!(Some("bb"), reader.next());
411        assert_eq!(Some("ccc"), reader.next());
412        assert_eq!(None, reader.next());
413    }
414
415    #[test]
416    fn reader_byte_count() {
417        let input = "aaaabbbbccccddd";
418        let mut reader = ItemReader::new(
419            input,
420            InFormatBuilder::default()
421                .item_separator(ItemSeparator::ByteCount(4))
422                .build()
423                .unwrap(),
424        );
425        assert_eq!(Some("aaaa"), reader.next());
426        assert_eq!(Some("bbbb"), reader.next());
427        assert_eq!(Some("cccc"), reader.next());
428        assert_eq!(None, reader.next());
429    }
430
431    #[test]
432    fn reader_explicit_multiline() {
433        let input = "aa,vvv,cccc,\nd,ee\n,a\n";
434        let mut reader = ItemReader::new(
435            input,
436            InFormatBuilder::default()
437                .item_separator(ItemSeparator::Explicit(",".to_string()))
438                .line_separator(Some(LineSeparator {
439                    items_per_line: 3,
440                    line_separator: "\n".to_string(),
441                }))
442                .build()
443                .unwrap(),
444        );
445        assert_eq!(Some("aa"), reader.next());
446        assert_eq!(Some("vvv"), reader.next());
447        assert_eq!(Some("cccc,"), reader.next());
448        assert_eq!(Some("d"), reader.next());
449        assert_eq!(Some("ee\n"), reader.next());
450        assert_eq!(Some("a"), reader.next());
451        assert_eq!(None, reader.next());
452    }
453
454    #[test]
455    fn reader_byte_count_multiline() {
456        let input = "aavvcc;ddeebb;";
457        let mut reader = ItemReader::new(
458            input,
459            InFormatBuilder::default()
460                .item_separator(ItemSeparator::ByteCount(2))
461                .line_separator(Some(LineSeparator {
462                    items_per_line: 3,
463                    line_separator: ";".to_string(),
464                }))
465                .build()
466                .unwrap(),
467        );
468        assert_eq!(Some("aa"), reader.next());
469        assert_eq!(Some("vv"), reader.next());
470        assert_eq!(Some("cc"), reader.next());
471        assert_eq!(Some("dd"), reader.next());
472        assert_eq!(Some("ee"), reader.next());
473        assert_eq!(Some("bb"), reader.next());
474        assert_eq!(None, reader.next());
475    }
476
477    #[test]
478    fn example_byte_count() {
479        let input = "aabbccdd";
480        let fmt = InFormatBuilder::default()
481            .item_separator(ItemSeparator::ByteCount(2))
482            .build()
483            .unwrap();
484        let mut it = read(input, fmt);
485        assert_eq!(Some("aa"), it.next());
486        assert_eq!(Some("bb"), it.next());
487        assert_eq!(Some("cc"), it.next());
488        assert_eq!(Some("dd"), it.next());
489        assert_eq!(None, it.next());
490    }
491
492    #[test]
493    fn example_explicit() {
494        let input = "πŸ‘‰πŸ‘‰πŸ‘‰SEP😊😊SEPπŸ––SEPπŸ’ΌπŸ’ΌπŸ’Ό";
495        let fmt = InFormatBuilder::default()
496            .item_separator(ItemSeparator::Explicit("SEP".to_string()))
497            .build()
498            .unwrap();
499        let mut it = read(input, fmt);
500        assert_eq!(Some("πŸ‘‰πŸ‘‰πŸ‘‰"), it.next());
501        assert_eq!(Some("😊😊"), it.next());
502        assert_eq!(Some("πŸ––"), it.next());
503        assert_eq!(Some("πŸ’ΌπŸ’ΌπŸ’Ό"), it.next());
504        assert_eq!(None, it.next());
505    }
506}