Skip to main content

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                && let Some(item) = inner.next() {
84                    return Some((self.outer_index - 1, item)); // -1 because we already incremented
85                }
86
87            // Current inner is exhausted (or None), get the next outer element
88            let next_outer = self.iter.next()?;
89            self.current_inner = Some(next_outer.into_iter());
90            self.outer_index += 1;
91            // Loop back to try the new inner iterator
92        }
93    }
94
95    fn size_hint(&self) -> (usize, Option<usize>) {
96        let (inner_lower, inner_upper) = self
97            .current_inner
98            .as_ref()
99            .map_or((0, None), |inner| inner.size_hint());
100
101        let (outer_lower, outer_upper) = self.iter.size_hint();
102
103        let lower = inner_lower.saturating_add(outer_lower);
104        let upper = match (inner_upper, outer_upper) {
105            (Some(i), Some(o)) => i.checked_add(o),
106            _ => None,
107        };
108
109        (lower, upper)
110    }
111}
112
113// Optional: implement FusedIterator if the underlying iterators do
114impl<I> FusedIterator for FlattenWithIndex<I>
115where
116    I: Iterator + FusedIterator,
117    I::Item: IntoIterator,
118    <I::Item as IntoIterator>::IntoIter: FusedIterator,
119{
120}
121
122pub trait FlattenWithIndexExt: Iterator {
123    fn flatten_with_index(self) -> FlattenWithIndex<Self>
124    where
125        Self::Item: IntoIterator,
126        Self: Sized,
127    {
128        FlattenWithIndex::new(self)
129    }
130}
131
132impl<T: Iterator> FlattenWithIndexExt for T {}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_flatten_with_index() {
140        // Test with a non-empty outer iterator with multiple non-empty inner iterators
141        let data = vec![vec!['a', 'b'], vec!['c', 'd'], vec!['e', 'f', 'g']];
142
143        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
144
145        let expected = vec![
146            (0, 'a'),
147            (0, 'b'),
148            (1, 'c'),
149            (1, 'd'),
150            (2, 'e'),
151            (2, 'f'),
152            (2, 'g'),
153        ];
154        assert_eq!(result, expected);
155
156        // Test with an empty outer iterator
157        let data: Vec<Vec<char>> = Vec::new();
158        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
159        assert!(result.is_empty());
160
161        // Test with an empty inner iterator (outer iterator is not empty)
162        let data = vec![
163            vec!['a', 'b'],
164            vec![], // Empty inner iterator
165            vec!['c', 'd'],
166        ];
167
168        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
169
170        let expected = vec![(0, 'a'), (0, 'b'), (2, 'c'), (2, 'd')];
171        assert_eq!(result, expected);
172
173        // Test with a mix of non-empty and empty inner iterators
174        let data = vec![
175            vec!['a', 'b'],
176            vec![], // Empty inner
177            vec!['c'],
178            vec![], // Empty inner
179            vec!['d', 'e', 'f'],
180        ];
181
182        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
183
184        let expected = vec![(0, 'a'), (0, 'b'), (2, 'c'), (4, 'd'), (4, 'e'), (4, 'f')];
185
186        assert_eq!(result, expected);
187
188        // Test with all empty inner iterators
189        let data = vec![vec![], vec![], vec![]];
190
191        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
192
193        assert!(result.is_empty());
194
195        // Test with just one element in the outer iterator
196        let data = vec![vec!['a', 'b', 'c']];
197
198        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
199
200        let expected = vec![(0, 'a'), (0, 'b'), (0, 'c')];
201
202        assert_eq!(result, expected);
203
204        // Test with just one element in the inner iterator (outer iterator has multiple elements)
205        let data = vec![
206            vec!['a'],      // Inner iterator has one element
207            vec!['b', 'c'], // Inner iterator has one element
208            vec!['d'],      // Inner iterator has one element
209        ];
210
211        let result: Vec<(usize, char)> = data.into_iter().flatten_with_index().collect();
212
213        let expected = vec![(0, 'a'), (1, 'b'), (1, 'c'), (2, 'd')];
214
215        assert_eq!(result, expected);
216    }
217}