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;
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}