tpnote_lib/
lib.rs

1//! The `tpnote-lib` library is designed to embed Tp-Note's core function in
2//! common text editors and text editor plugins. It is dealing with templates
3//! and input files and is also part of the command line application
4//! [Tp-Note](https://blog.getreu.net/projects/tp-note/). This library also
5//! provides a default configuration in the static variable `LIB_CFG` that can
6//! be customized at runtime. The defaults for the variables grouped in
7//! `LIB_CFG`, are defined as constants in the module `config` (see Rustdoc).
8//! While `LIB_CFG` is sourced only once at the start of Tp-Note, the
9//! `SETTINGS` may be sourced more often. The latter contains configuration
10//! data originating form environment variables.
11//!
12//! Tp-Note's high-level API, cf. module `workflow`, abstracts most
13//! implementation details. Roughly speaking, the input path correspond to
14//! _Tp-Note's_ first positional command line parameter and the output path is
15//! the same that is printed to standard output after usage. The main
16//! consumer of `tpnote-lib`'s high-level API is the module `workflow` and
17//! `html_renderer` in the `tpnote` crate.
18//!
19pub mod clone_ext;
20pub mod config;
21pub mod config_value;
22pub mod content;
23pub mod context;
24pub mod error;
25pub mod filename;
26mod filter;
27mod front_matter;
28#[cfg(feature = "renderer")]
29pub mod highlight;
30pub mod html;
31#[cfg(feature = "renderer")]
32pub mod html2md;
33pub mod html_renderer;
34#[cfg(feature = "lang-detection")]
35pub mod lingua;
36pub mod markup_language;
37mod note;
38pub mod settings;
39pub mod template;
40pub mod text_reader;
41pub mod workflow;
42
43use std::iter::FusedIterator;
44
45/// An iterator adapter that flattens an iterator of iterators,
46/// while providing the index of the current outer (inner-producing) element.
47pub struct FlattenWithIndex<I>
48where
49    I: Iterator,
50    I::Item: IntoIterator,
51{
52    iter: I,
53    current_inner: Option<<I::Item as IntoIterator>::IntoIter>,
54    outer_index: usize, // This is the counter you asked for
55}
56
57impl<I> FlattenWithIndex<I>
58where
59    I: Iterator,
60    I::Item: IntoIterator,
61{
62    /// Creates a new `FlattenWithIndex`.
63    pub fn new(iter: I) -> Self {
64        Self {
65            iter,
66            current_inner: None,
67            outer_index: 0,
68        }
69    }
70}
71
72impl<I> Iterator for FlattenWithIndex<I>
73where
74    I: Iterator,
75    I::Item: IntoIterator,
76{
77    type Item = (usize, <I::Item as IntoIterator>::Item);
78
79    fn next(&mut self) -> Option<Self::Item> {
80        loop {
81            // If we have a current inner iterator, try to get the next element from it
82            if let Some(inner) = &mut self.current_inner {
83                if let Some(item) = inner.next() {
84                    return Some((self.outer_index - 1, item)); // -1 because we already incremented
85                }
86            }
87
88            // Current inner is exhausted (or None), get the next outer element
89            let next_outer = self.iter.next()?;
90            self.current_inner = Some(next_outer.into_iter());
91            self.outer_index += 1;
92            // Loop back to try the new inner iterator
93        }
94    }
95
96    fn size_hint(&self) -> (usize, Option<usize>) {
97        let (inner_lower, inner_upper) = self
98            .current_inner
99            .as_ref()
100            .map_or((0, None), |inner| inner.size_hint());
101
102        let (outer_lower, outer_upper) = self.iter.size_hint();
103
104        let lower = inner_lower.saturating_add(outer_lower);
105        let upper = match (inner_upper, outer_upper) {
106            (Some(i), Some(o)) => i.checked_add(o),
107            _ => None,
108        };
109
110        (lower, upper)
111    }
112}
113
114// Optional: implement FusedIterator if the underlying iterators do
115impl<I> FusedIterator for FlattenWithIndex<I>
116where
117    I: Iterator + FusedIterator,
118    I::Item: IntoIterator,
119    <I::Item as IntoIterator>::IntoIter: FusedIterator,
120{
121}
122
123pub trait FlattenWithIndexExt: Iterator {
124    fn flatten_with_index(self) -> FlattenWithIndex<Self>
125    where
126        Self::Item: IntoIterator,
127        Self: Sized,
128    {
129        FlattenWithIndex::new(self)
130    }
131}
132
133impl<T: Iterator> FlattenWithIndexExt for T {}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_flatten_with_index() {
141        // Test with a non-empty outer iterator with multiple non-empty inner iterators
142        let data = vec![vec!['a', 'b'], vec!['c', 'd'], vec!['e', 'f', 'g']];
143
144        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
145
146        let expected = vec![
147            (0, 'a'),
148            (0, 'b'),
149            (1, 'c'),
150            (1, 'd'),
151            (2, 'e'),
152            (2, 'f'),
153            (2, 'g'),
154        ];
155        assert_eq!(result, expected);
156
157        // Test with an empty outer iterator
158        let data: Vec<Vec<char>> = Vec::new();
159        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
160        assert!(result.is_empty());
161
162        // Test with an empty inner iterator (outer iterator is not empty)
163        let data = vec![
164            vec!['a', 'b'],
165            vec![], // Empty inner iterator
166            vec!['c', 'd'],
167        ];
168
169        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
170
171        let expected = vec![(0, 'a'), (0, 'b'), (2, 'c'), (2, 'd')];
172        assert_eq!(result, expected);
173
174        // Test with a mix of non-empty and empty inner iterators
175        let data = vec![
176            vec!['a', 'b'],
177            vec![], // Empty inner
178            vec!['c'],
179            vec![], // Empty inner
180            vec!['d', 'e', 'f'],
181        ];
182
183        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
184
185        let expected = vec![(0, 'a'), (0, 'b'), (2, 'c'), (4, 'd'), (4, 'e'), (4, 'f')];
186
187        assert_eq!(result, expected);
188
189        // Test with all empty inner iterators
190        let data = vec![vec![], vec![], vec![]];
191
192        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
193
194        assert!(result.is_empty());
195
196        // Test with just one element in the outer iterator
197        let data = vec![vec!['a', 'b', 'c']];
198
199        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
200
201        let expected = vec![(0, 'a'), (0, 'b'), (0, 'c')];
202
203        assert_eq!(result, expected);
204
205        // Test with just one element in the inner iterator (outer iterator has multiple elements)
206        let data = vec![
207            vec!['a'], // Inner iterator has one element
208            vec!['b', 'c'], // Inner iterator has one element
209            vec!['d'], // Inner iterator has one element
210        ];
211
212        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
213
214        let expected = vec![(0, 'a'), (1, 'b'), (1, 'c'), (2, 'd')];
215
216        assert_eq!(result, expected);
217    }
218}