gobby-wiki 0.8.0

Gobby wiki CLI shell
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ProgressPhase {
    IngestFile,
    IngestUrl,
    SessionArchive,
    VaultIndex,
    VectorSync,
    GraphSync,
}

pub(crate) trait ProgressSink {
    fn start(&mut self, phase: ProgressPhase, total: usize);
    fn advance(&mut self, phase: ProgressPhase, item: &str);
    fn finish(&mut self, phase: ProgressPhase);
}

#[derive(Default)]
pub(crate) struct ProgressOptions<'a> {
    pub(crate) sink: Option<&'a mut dyn ProgressSink>,
}

impl<'a> ProgressOptions<'a> {
    pub(crate) fn with_sink(sink: &'a mut dyn ProgressSink) -> Self {
        Self { sink: Some(sink) }
    }

    fn start(&mut self, phase: ProgressPhase, total: usize) {
        if let Some(sink) = self.sink.as_deref_mut() {
            sink.start(phase, total);
        }
    }

    fn advance(&mut self, phase: ProgressPhase, item: &str) {
        if let Some(sink) = self.sink.as_deref_mut() {
            sink.advance(phase, item);
        }
    }

    fn finish(&mut self, phase: ProgressPhase) {
        if let Some(sink) = self.sink.as_deref_mut() {
            sink.finish(phase);
        }
    }
}

pub(crate) struct ActiveProgress<'a, 'b> {
    options: &'a mut ProgressOptions<'b>,
    phase: ProgressPhase,
    started: bool,
}

impl<'a, 'b> ActiveProgress<'a, 'b> {
    pub(crate) fn new(
        options: &'a mut ProgressOptions<'b>,
        phase: ProgressPhase,
        total: usize,
    ) -> Self {
        let progress = Self {
            options,
            phase,
            started: total > 0,
        };
        if progress.started {
            progress.options.start(phase, total);
        }
        progress
    }

    pub(crate) fn advance(&mut self, item: &str) {
        self.options.advance(self.phase, item);
    }
}

impl Drop for ActiveProgress<'_, '_> {
    fn drop(&mut self) {
        if self.started {
            self.options.finish(self.phase);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Default)]
    struct RecordingProgress {
        events: Vec<String>,
    }

    impl ProgressSink for RecordingProgress {
        fn start(&mut self, phase: ProgressPhase, total: usize) {
            self.events.push(format!("{phase:?}:start:{total}"));
        }

        fn advance(&mut self, phase: ProgressPhase, item: &str) {
            self.events.push(format!("{phase:?}:advance:{item}"));
        }

        fn finish(&mut self, phase: ProgressPhase) {
            self.events.push(format!("{phase:?}:finish"));
        }
    }

    #[test]
    fn active_progress_reports_start_advance_finish() {
        let mut sink = RecordingProgress::default();
        {
            let mut options = ProgressOptions::with_sink(&mut sink);
            let mut progress = ActiveProgress::new(&mut options, ProgressPhase::VaultIndex, 2);
            progress.advance("a.md");
            progress.advance("b.md");
        }

        assert_eq!(
            sink.events,
            vec![
                "VaultIndex:start:2",
                "VaultIndex:advance:a.md",
                "VaultIndex:advance:b.md",
                "VaultIndex:finish"
            ]
        );
    }
}