intelli_shell/process/
tldr_fetch.rs

1use std::{
2    collections::HashMap,
3    io::{BufRead, BufReader},
4    time::Duration,
5};
6
7use color_eyre::eyre::Context;
8use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
9use tokio::sync::mpsc;
10use tokio_util::sync::CancellationToken;
11
12use super::{Process, ProcessOutput};
13use crate::{
14    cli::TldrFetchProcess,
15    config::Config,
16    errors::AppError,
17    format_error,
18    service::{IntelliShellService, RepoStatus, TldrFetchProgress},
19    widgets::SPINNER_CHARS,
20};
21
22impl Process for TldrFetchProcess {
23    async fn execute(
24        self,
25        config: Config,
26        service: IntelliShellService,
27        cancellation_token: CancellationToken,
28    ) -> color_eyre::Result<ProcessOutput> {
29        let mut commands = self.commands;
30        if let Some(filter_commands) = self.filter_commands {
31            let content = filter_commands
32                .into_reader()
33                .wrap_err("Couldn't read filter commands file")?;
34            let reader = BufReader::new(content);
35            for line in reader.lines() {
36                let line = line.wrap_err("Failed to read line from filter commands")?;
37                let trimmed = line.trim();
38                if !trimmed.is_empty() && !trimmed.starts_with("#") && !trimmed.starts_with("//") {
39                    commands.push(trimmed.to_string());
40                }
41            }
42        }
43
44        let (tx, mut rx) = mpsc::channel(32);
45
46        let service_handle = tokio::spawn(async move {
47            service
48                .fetch_tldr_commands(self.category, commands, tx, cancellation_token)
49                .await
50        });
51
52        let m = MultiProgress::new();
53        let pb1 = m.add(ProgressBar::new_spinner());
54        pb1.set_style(style_active());
55        pb1.set_prefix("[1/3]");
56        pb1.enable_steady_tick(Duration::from_millis(100));
57
58        let pb2 = m.add(ProgressBar::new_spinner());
59        pb2.set_style(style_active());
60
61        let pb3 = m.add(ProgressBar::new(0));
62        let spinner_style = ProgressStyle::with_template("      {spinner:.dim.white} {msg}").unwrap();
63        let mut file_spinners: HashMap<String, ProgressBar> = HashMap::new();
64
65        while let Some(progress) = rx.recv().await {
66            match progress {
67                TldrFetchProgress::Repository(status) => {
68                    let (msg, done) = match status {
69                        RepoStatus::Cloning => ("Cloning tldr repository ...", false),
70                        RepoStatus::DoneCloning => ("Cloned tldr repository", true),
71                        RepoStatus::Fetching => ("Fetching latest tldr changes ...", false),
72                        RepoStatus::UpToDate => ("Up-to-date tldr repository", true),
73                        RepoStatus::Updating => ("Updating tldr repository ...", false),
74                        RepoStatus::DoneUpdating => ("Updated tldr repository", true),
75                    };
76                    pb1.set_message(msg);
77                    if done {
78                        pb1.set_style(style_done());
79                        pb1.finish();
80                    }
81                }
82                TldrFetchProgress::LocatingFiles => {
83                    pb2.set_prefix("[2/3]");
84                    pb2.set_message("Locating files ...");
85                    pb2.enable_steady_tick(Duration::from_millis(100));
86                }
87                TldrFetchProgress::FilesLocated(count) => {
88                    pb2.set_style(style_done());
89                    pb2.finish_with_message(format!("Found {count} files"));
90                }
91                TldrFetchProgress::ProcessingStart(total) => {
92                    pb3.set_length(total);
93                    pb3.set_style(
94                        ProgressStyle::with_template("{prefix:.blue.bold} [{bar:40.cyan/blue}] {pos}/{len} {wide_msg}")
95                            .unwrap()
96                            .progress_chars("##-"),
97                    );
98                    pb3.set_prefix("[3/3]");
99                    pb3.set_message("Processing files ...");
100                }
101                TldrFetchProgress::ProcessingFile(command) => {
102                    let spinner = m.add(ProgressBar::new_spinner());
103                    spinner.set_style(spinner_style.clone());
104                    spinner.set_message(format!("Processing {command} ..."));
105                    file_spinners.insert(command, spinner);
106                }
107                TldrFetchProgress::FileProcessed(command) => {
108                    if let Some(spinner) = file_spinners.remove(&command) {
109                        spinner.finish_and_clear();
110                    }
111                    pb3.inc(1);
112                }
113            }
114        }
115
116        pb3.set_style(style_done());
117        pb3.finish_with_message("Done processing files");
118
119        match service_handle.await? {
120            Ok(stats) => Ok(stats.into_output(&config.theme)),
121            Err(AppError::UserFacing(err)) => Ok(ProcessOutput::fail().stderr(format_error!(config.theme, "{err}"))),
122            Err(AppError::Unexpected(report)) => Err(report),
123        }
124    }
125}
126
127fn style_active() -> ProgressStyle {
128    ProgressStyle::with_template("{prefix:.blue.bold} {spinner} {wide_msg}")
129        .unwrap()
130        .tick_strings(&SPINNER_CHARS)
131}
132
133fn style_done() -> ProgressStyle {
134    ProgressStyle::with_template("{prefix:.green.bold} {wide_msg}").unwrap()
135}