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