cargo-sync-rdme 0.5.0

Cargo subcommand to synchronize README with crate documentation
Documentation
use std::{borrow::Cow, iter, ops::Range};

use super::{super::contents::Contents, Marker, Replace};

pub(in super::super) fn replace_all(
    text: &str,
    markers: &[(Replace, Range<usize>)],
    contents: &[Contents],
) -> String {
    let pairs = markers
        .iter()
        .zip(contents)
        .map(|((replace, range), contents)| ((replace.clone(), contents), range.clone()));

    interpolate_ranges(0..text.len(), pairs)
        .map(|(contents, range)| match contents {
            Some((replace, contents)) => {
                if contents.text().is_empty() {
                    Cow::Owned(format!("{}\n", Marker::Replace(replace)))
                } else {
                    Cow::Owned(format!(
                        "{}\n{}{}\n",
                        Marker::Start(replace),
                        contents.text(),
                        Marker::End
                    ))
                }
            }
            None => Cow::Borrowed(&text[range]),
        })
        .collect()
}

fn interpolate_ranges<T>(
    range: Range<usize>,
    items: impl IntoIterator<Item = (T, Range<usize>)>,
) -> impl Iterator<Item = (Option<T>, Range<usize>)> {
    let mut items = items.into_iter().peekable();
    let mut offset = range.start;
    iter::from_fn(move || match items.peek() {
        Some(&(_, Range { start, .. })) if offset < start => {
            let range = offset..start;
            offset = start;
            Some((None, range))
        }
        Some(_) => {
            let (item, Range { start, end }) = items.next().unwrap();
            offset = end;
            Some((Some(item), start..end))
        }
        None if offset < range.end => {
            let range = offset..range.end;
            offset = range.end;
            Some((None, range))
        }
        None => None,
    })
}

#[cfg(test)]
mod tests {
    #[test]
    fn interpolate_ranges() {
        let items = [(1, 0..1), (2, 1..2), (3, 2..3)];
        let ranges = super::interpolate_ranges(0..3, items);
        assert_eq!(
            ranges.collect::<Vec<_>>(),
            vec![(Some(1), 0..1), (Some(2), 1..2), (Some(3), 2..3),]
        );

        let items = [(1, 3..4), (2, 4..5), (3, 6..7), (4, 8..9)];
        let ranges = super::interpolate_ranges(0..10, items);
        assert_eq!(
            ranges.collect::<Vec<_>>(),
            vec![
                (None, 0..3),
                (Some(1), 3..4),
                (Some(2), 4..5),
                (None, 5..6),
                (Some(3), 6..7),
                (None, 7..8),
                (Some(4), 8..9),
                (None, 9..10),
            ]
        );
    }
}