use crate::manifest::Manifest;
use indicatif::{ProgressBar as IndicatifBar, ProgressStyle as IndicatifStyle};
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[deprecated(since = "0.3.0", note = "Use MultiPhaseProgress instead")]
pub use indicatif::ProgressBar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstallationPhase {
SyncingSources,
ResolvingDependencies,
Installing,
InstallingResources,
Finalizing,
}
impl InstallationPhase {
pub const fn description(&self) -> &'static str {
match self {
Self::SyncingSources => "Syncing sources",
Self::ResolvingDependencies => "Resolving dependencies",
Self::Installing => "Installing resources",
Self::InstallingResources => "Installing resources",
Self::Finalizing => "Finalizing installation",
}
}
pub const fn spinner_prefix(&self) -> &'static str {
match self {
Self::SyncingSources => "⏳",
Self::ResolvingDependencies => "🔍",
Self::Installing => "📦",
Self::InstallingResources => "📦",
Self::Finalizing => "✨",
}
}
}
#[derive(Clone)]
pub struct MultiPhaseProgress {
multi: Arc<indicatif::MultiProgress>,
current_bar: Arc<Mutex<Option<IndicatifBar>>>,
enabled: bool,
}
impl MultiPhaseProgress {
pub fn new(enabled: bool) -> Self {
Self {
multi: Arc::new(indicatif::MultiProgress::new()),
current_bar: Arc::new(Mutex::new(None)),
enabled,
}
}
pub fn start_phase(&self, phase: InstallationPhase, message: Option<&str>) {
if !self.enabled {
if !self.enabled {
return;
}
let phase_msg = if let Some(msg) = message {
format!("{} {} {}", phase.spinner_prefix(), phase.description(), msg)
} else {
format!("{} {}", phase.spinner_prefix(), phase.description())
};
println!("{phase_msg}");
return;
}
if let Ok(mut guard) = self.current_bar.lock() {
*guard = None;
}
let spinner = self.multi.add(IndicatifBar::new_spinner());
let phase_msg =
format!("{} {} {}", phase.spinner_prefix(), phase.description(), message.unwrap_or(""));
let style = IndicatifStyle::default_spinner()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ")
.template("{spinner} {msg}")
.unwrap();
spinner.set_style(style);
spinner.set_message(phase_msg);
spinner.enable_steady_tick(Duration::from_millis(100));
*self.current_bar.lock().unwrap() = Some(spinner);
}
pub fn start_phase_with_progress(&self, phase: InstallationPhase, total: usize) {
if !self.enabled {
if !self.enabled {
return;
}
println!("{} {} (0/{})", phase.spinner_prefix(), phase.description(), total);
return;
}
if let Ok(mut guard) = self.current_bar.lock() {
*guard = None;
}
let progress_bar = self.multi.add(IndicatifBar::new(total as u64));
let style = IndicatifStyle::default_bar()
.template(&format!(
"{} {{msg}} [{{bar:40.cyan/blue}}] {{pos}}/{{len}}",
phase.spinner_prefix()
))
.unwrap()
.progress_chars("=>-");
progress_bar.set_style(style);
progress_bar.set_message(phase.description());
*self.current_bar.lock().unwrap() = Some(progress_bar);
}
pub fn update_message(&self, message: String) {
if let Ok(guard) = self.current_bar.lock()
&& let Some(ref bar) = *guard
{
bar.set_message(message);
}
}
pub fn update_current_message(&self, message: &str) {
if let Ok(guard) = self.current_bar.lock()
&& let Some(ref bar) = *guard
{
bar.set_message(message.to_string());
}
}
pub fn increment_progress(&self, delta: u64) {
if let Ok(guard) = self.current_bar.lock()
&& let Some(ref bar) = *guard
{
bar.inc(delta);
}
}
pub fn set_progress(&self, pos: usize) {
if let Ok(guard) = self.current_bar.lock()
&& let Some(ref bar) = *guard
{
bar.set_position(pos as u64);
}
}
pub fn complete_phase(&self, message: Option<&str>) {
if !self.enabled {
if !self.enabled {
return;
}
if let Some(msg) = message {
println!("✓ {msg}");
}
return;
}
if let Ok(mut guard) = self.current_bar.lock()
&& let Some(bar) = guard.take()
{
bar.disable_steady_tick();
let final_message = if let Some(msg) = message {
format!("✓ {msg}")
} else {
"✓ Phase complete".to_string()
};
bar.finish_and_clear();
self.multi.suspend(|| {
println!("{final_message}");
});
}
}
pub fn clear(&self) {
if let Ok(mut guard) = self.current_bar.lock()
&& let Some(bar) = guard.take()
{
bar.finish_and_clear();
}
self.multi.clear().ok();
}
pub fn add_progress_bar(&self, total: u64) -> Option<IndicatifBar> {
if !self.enabled {
return None;
}
let pb = self.multi.add(IndicatifBar::new(total));
let style = IndicatifStyle::default_bar()
.template(" {msg} [{bar:40.cyan/blue}] {pos}/{len}")
.unwrap()
.progress_chars("=>-");
pb.set_style(style);
Some(pb)
}
}
pub fn collect_dependency_names(manifest: &Manifest) -> Vec<String> {
manifest.all_dependencies().iter().map(|(name, _)| (*name).to_string()).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_installation_phase_description() {
assert_eq!(InstallationPhase::SyncingSources.description(), "Syncing sources");
assert_eq!(
InstallationPhase::ResolvingDependencies.description(),
"Resolving dependencies"
);
assert_eq!(InstallationPhase::Installing.description(), "Installing resources");
assert_eq!(InstallationPhase::InstallingResources.description(), "Installing resources");
assert_eq!(InstallationPhase::Finalizing.description(), "Finalizing installation");
}
#[test]
fn test_installation_phase_spinner_prefix() {
assert_eq!(InstallationPhase::SyncingSources.spinner_prefix(), "⏳");
assert_eq!(InstallationPhase::ResolvingDependencies.spinner_prefix(), "🔍");
assert_eq!(InstallationPhase::Installing.spinner_prefix(), "📦");
assert_eq!(InstallationPhase::InstallingResources.spinner_prefix(), "📦");
assert_eq!(InstallationPhase::Finalizing.spinner_prefix(), "✨");
}
#[test]
fn test_multi_phase_progress_new() {
let progress = MultiPhaseProgress::new(true);
progress.start_phase(InstallationPhase::SyncingSources, Some("test message"));
progress.update_current_message("updated message");
progress.complete_phase(Some("completed"));
progress.clear();
}
#[test]
fn test_multi_phase_progress_with_progress_bar() {
let progress = MultiPhaseProgress::new(true);
progress.start_phase_with_progress(InstallationPhase::Installing, 10);
progress.increment_progress(5);
progress.set_progress(8);
progress.complete_phase(Some("Installation completed"));
}
#[test]
fn test_multi_phase_progress_disabled() {
let progress = MultiPhaseProgress::new(false);
progress.start_phase(InstallationPhase::SyncingSources, None);
progress.complete_phase(Some("test"));
progress.clear();
}
#[test]
fn test_collect_dependency_names() {
}
}