use std::borrow::Cow;
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use owo_colors::OwoColorize;
use rustc_hash::FxHashMap;
use crate::hook::Hook;
use crate::printer::Printer;
use crate::workspace;
static CURRENT_REPORTER: Mutex<Option<Weak<ProgressReporter>>> = Mutex::new(None);
pub(crate) const SPINNER_TICKS: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
pub(crate) fn set_current_reporter(reporter: Option<&Arc<ProgressReporter>>) {
*CURRENT_REPORTER.lock().unwrap() = reporter.map(Arc::downgrade);
}
pub(crate) fn suspend(f: impl FnOnce() + Send + 'static) {
let reporter = CURRENT_REPORTER.lock().unwrap().clone();
match reporter.and_then(|r| r.upgrade()) {
Some(reporter) => reporter.children.suspend(f),
None => f(),
}
}
#[derive(Default, Debug)]
struct BarState {
bars: FxHashMap<usize, ProgressBar>,
id: usize,
}
impl BarState {
fn id(&mut self) -> usize {
self.id += 1;
self.id
}
}
pub(crate) struct ProgressReporter {
pub(crate) printer: Printer,
pub(crate) root: ProgressBar,
state: Arc<Mutex<BarState>>,
pub(crate) children: MultiProgress,
}
impl ProgressReporter {
fn new(root: ProgressBar, children: MultiProgress, printer: Printer) -> Self {
Self {
printer,
root,
state: Arc::default(),
children,
}
}
fn on_start(&self, msg: impl Into<Cow<'static, str>>) -> usize {
let mut state = self.state.lock().unwrap();
let id = state.id();
let progress = self.children.insert_before(
&self.root,
ProgressBar::with_draw_target(None, self.printer.target()),
);
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
progress.set_message(msg);
state.bars.insert(id, progress);
id
}
pub(crate) fn next_id(&self) -> usize {
self.state.lock().unwrap().id()
}
fn on_progress(&self, id: usize) {
let progress = {
let mut state = self.state.lock().unwrap();
state.bars.remove(&id).unwrap()
};
self.root.inc(1);
progress.finish_and_clear();
}
pub(crate) fn set_root_prefix(&self, prefix: impl Into<Cow<'static, str>>) {
self.root.set_prefix(prefix);
}
pub(crate) fn on_complete(&self) {
self.root.set_prefix("");
self.root.set_message("");
self.root.finish_and_clear();
}
}
impl From<Printer> for ProgressReporter {
fn from(printer: Printer) -> Self {
let multi = MultiProgress::with_draw_target(printer.target());
let root = multi.add(ProgressBar::with_draw_target(None, printer.target()));
root.enable_steady_tick(Duration::from_millis(200));
root.set_style(
ProgressStyle::with_template(
"{spinner:.cyan.bold.dim} {prefix:.cyan.bold.dim}{msg:.dim}",
)
.unwrap()
.tick_strings(SPINNER_TICKS),
);
Self::new(root, multi, printer)
}
}
pub(crate) struct HookInitReporter {
reporter: Arc<ProgressReporter>,
}
impl HookInitReporter {
pub(crate) fn new(printer: Printer) -> Self {
let reporter = Arc::new(ProgressReporter::from(printer));
set_current_reporter(Some(&reporter));
Self { reporter }
}
}
impl workspace::HookInitReporter for HookInitReporter {
fn on_clone_start(&self, repo: &str) -> usize {
self.reporter.set_root_prefix("Cloning repos...");
self.reporter
.on_start(format!("{} {}", "Cloning".bold().cyan(), repo.dimmed()))
}
fn on_clone_complete(&self, id: usize) {
self.reporter.on_progress(id);
}
fn on_complete(&self) {
self.reporter.on_complete();
}
}
pub(crate) struct HookInstallReporter {
reporter: Arc<ProgressReporter>,
}
impl HookInstallReporter {
pub(crate) fn new(printer: Printer) -> Self {
let reporter = Arc::new(ProgressReporter::from(printer));
set_current_reporter(Some(&reporter));
Self { reporter }
}
pub fn on_install_start(&self, hook: &Hook) -> usize {
self.reporter.set_root_prefix("Installing hooks...");
self.reporter.on_start(format!(
"{} {}",
"Installing".bold().cyan(),
hook.id.dimmed(),
))
}
pub fn on_install_complete(&self, id: usize) {
self.reporter.on_progress(id);
}
pub fn on_complete(&self) {
self.reporter.on_complete();
}
}
#[derive(Clone)]
pub(crate) struct UpdateReporter {
reporter: Arc<ProgressReporter>,
}
impl UpdateReporter {
pub(crate) fn new(printer: Printer) -> Self {
let reporter = Arc::new(ProgressReporter::from(printer));
set_current_reporter(Some(&reporter));
Self { reporter }
}
}
impl UpdateReporter {
pub fn on_update_start(&self, repo: &str) -> usize {
self.reporter.set_root_prefix("Updating repos...");
self.reporter
.on_start(format!("{} {}", "Updating".bold().cyan(), repo.dimmed()))
}
pub fn on_update_complete(&self, id: usize) {
self.reporter.on_progress(id);
}
pub fn on_complete(&self) {
self.reporter.on_complete();
}
}
#[derive(Debug)]
pub(crate) struct CleaningReporter {
bar: ProgressBar,
}
impl CleaningReporter {
pub(crate) fn new(printer: Printer, max: usize) -> Self {
let bar = ProgressBar::with_draw_target(Some(max as u64), printer.target());
bar.set_style(
ProgressStyle::with_template("{prefix} [{bar:20}] {percent}%")
.unwrap()
.progress_chars("=> "),
);
bar.set_prefix(format!("{}", "Cleaning".bold().cyan()));
Self { bar }
}
}
impl CleaningReporter {
pub(crate) fn on_clean(&self) {
self.bar.inc(1);
}
pub(crate) fn on_complete(&self) {
self.bar.finish_and_clear();
}
}