intelli_shell/process/
tldr_fetch.rs1use 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}