Skip to content

Commit

Permalink
Add progress bar for hook init and install
Browse files Browse the repository at this point in the history
  • Loading branch information
j178 committed Dec 6, 2024
1 parent 5d404a7 commit d244af4
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 52 deletions.
7 changes: 5 additions & 2 deletions src/cli/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use indoc::indoc;
use owo_colors::OwoColorize;
use same_file::is_same_file;

use crate::cli::reporter::{HookInitReporter, HookInstallReporter};
use crate::cli::run;
use crate::cli::{ExitStatus, HookType};
use crate::fs::Simplified;
Expand Down Expand Up @@ -64,8 +65,10 @@ pub(crate) async fn install(
let store = Store::from_settings()?.init()?;
let _lock = store.lock_async().await?;

let hooks = project.init_hooks(&store, printer).await?;
run::install_hooks(&hooks, printer).await?;
let reporter = HookInitReporter::from(printer);
let hooks = project.init_hooks(&store, &reporter).await?;
let reporter = HookInstallReporter::from(printer);
run::install_hooks(&hooks, &reporter).await?;
}

Ok(ExitStatus::Success)
Expand Down
1 change: 1 addition & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::config::{HookType, Stage};
mod clean;
mod hook_impl;
mod install;
mod reporter;
mod run;
mod sample_config;
mod self_update;
Expand Down
167 changes: 167 additions & 0 deletions src/cli/reporter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use owo_colors::OwoColorize;

use crate::hook;
use crate::hook::Hook;
use crate::printer::Printer;

#[derive(Default, Debug)]
struct BarState {
/// A map of progress bars, by ID.
bars: HashMap<usize, ProgressBar>,
/// A monotonic counter for bar IDs.
id: usize,
}

impl BarState {
/// Returns a unique ID for a new progress bar.
fn id(&mut self) -> usize {
self.id += 1;
self.id
}
}

struct SubProgress {
state: Arc<Mutex<BarState>>,
multi: MultiProgress,
}

pub(crate) struct HookInitReporter {
printer: Printer,
root: ProgressBar,
sub: SubProgress,
}

impl HookInitReporter {
pub(crate) fn new(root: ProgressBar, multi_progress: MultiProgress, printer: Printer) -> Self {
Self {
printer,
root,
sub: SubProgress {
state: Arc::default(),
multi: multi_progress,
},
}
}
}

impl From<Printer> for HookInitReporter {
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:.white} {msg:.dim}")
.unwrap()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
);
root.set_message("Initializing hooks...");
Self::new(root, multi, printer)
}
}

impl hook::HookInitReporter for HookInitReporter {
fn on_repo_clone_start(&self, repo: &str) -> usize {
let mut state = self.sub.state.lock().unwrap();
let id = state.id();

let progress = self.sub.multi.insert_before(
&self.root,
ProgressBar::with_draw_target(None, self.printer.target()),
);

progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
progress.set_message(format!("{} {}", "Cloning".bold().cyan(), repo.dimmed(),));

state.bars.insert(id, progress);
id
}

fn on_repo_clone_complete(&self, id: usize) {
let progress = {
let mut state = self.sub.state.lock().unwrap();
state.bars.remove(&id).unwrap()
};

self.root.inc(1);
progress.finish_and_clear();
}

fn on_complete(&self) {
self.root.set_message("");
self.root.finish_and_clear();
}
}

pub struct HookInstallReporter {
printer: Printer,
root: ProgressBar,
sub: SubProgress,
}

impl HookInstallReporter {
pub fn new(root: ProgressBar, multi_progress: MultiProgress, printer: Printer) -> Self {
Self {
printer,
root,
sub: SubProgress {
state: Arc::default(),
multi: multi_progress,
},
}
}

pub fn on_install_start(&self, hook: &Hook) -> usize {
let mut state = self.sub.state.lock().unwrap();
let id = state.id();

let progress = self.sub.multi.insert_before(
&self.root,
ProgressBar::with_draw_target(None, self.printer.target()),
);

progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
progress.set_message(format!(
"{} {}",
"Installing".bold().cyan(),
hook.id.dimmed(),
));

state.bars.insert(id, progress);
id
}

pub fn on_install_complete(&self, id: usize) {
let progress = {
let mut state = self.sub.state.lock().unwrap();
state.bars.remove(&id).unwrap()
};

self.root.inc(1);
progress.finish_and_clear();
}

pub fn on_complete(&self) {
self.root.set_message("");
self.root.finish_and_clear();
}
}

impl From<Printer> for HookInstallReporter {
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:.white} {msg:.dim}")
.unwrap()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
);
root.set_message("Installing hooks...");
Self::new(root, multi, printer)
}
}
29 changes: 17 additions & 12 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use owo_colors::OwoColorize;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use tracing::{debug, trace};

use crate::cli::reporter::{HookInitReporter, HookInstallReporter};
use crate::cli::{ExitStatus, RunExtraArgs};
use crate::config::Stage;
use crate::fs::{normalize_path, Simplified};
Expand Down Expand Up @@ -67,8 +68,10 @@ pub(crate) async fn run(
let mut project = Project::new(config_file)?;
let store = Store::from_settings()?.init()?;

let reporter = HookInitReporter::from(printer);

let lock = store.lock_async().await?;
let hooks = project.init_hooks(&store, printer).await?;
let hooks = project.init_hooks(&store, &reporter).await?;

let hooks: Vec<_> = hooks
.into_iter()
Expand Down Expand Up @@ -117,7 +120,8 @@ pub(crate) async fn run(
"Hooks going to run: {:?}",
to_run.iter().map(|h| &h.id).collect::<Vec<_>>()
);
install_hooks(&to_run, printer).await?;
let reporter = HookInstallReporter::from(printer);
install_hooks(&to_run, &reporter).await?;
drop(lock);

// Clear any unstaged changes from the git working directory.
Expand Down Expand Up @@ -302,12 +306,7 @@ async fn all_filenames(
Ok(files)
}

async fn install_hook(hook: &Hook, env_dir: PathBuf, printer: Printer) -> Result<()> {
writeln!(
printer.stdout(),
"Installing environment for {}",
hook.repo(),
)?;
async fn install_hook(hook: &Hook, env_dir: PathBuf) -> Result<()> {
debug!(%hook, target = %env_dir.display(), "Install environment");

if env_dir.try_exists()? {
Expand All @@ -324,8 +323,7 @@ async fn install_hook(hook: &Hook, env_dir: PathBuf, printer: Printer) -> Result
Ok(())
}

// TODO: progress bar
pub async fn install_hooks(hooks: &[Hook], printer: Printer) -> Result<()> {
pub async fn install_hooks(hooks: &[Hook], reporter: &HookInstallReporter) -> Result<()> {
let to_install = hooks
.iter()
.filter(|&hook| !hook.installed())
Expand All @@ -334,12 +332,19 @@ pub async fn install_hooks(hooks: &[Hook], printer: Printer) -> Result<()> {
let mut tasks = FuturesUnordered::new();
for hook in to_install {
if let Some(env_dir) = hook.environment_dir() {
tasks.push(async move { install_hook(hook, env_dir, printer).await });
tasks.push(async move {
let progress = reporter.on_install_start(hook);
let result = install_hook(hook, env_dir).await;
(result, progress)
});
}
}
while let Some(result) = tasks.next().await {
while let Some((result, progress)) = tasks.next().await {
reporter.on_install_complete(progress);
result?;
}

reporter.on_complete();

Ok(())
}
18 changes: 18 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,18 +381,36 @@ pub struct ConfigRemoteRepo {
pub hooks: Vec<ConfigRemoteHook>,
}

impl Display for ConfigRemoteRepo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}@{}", self.repo, self.rev)
}
}

#[derive(Debug, Clone)]
pub struct ConfigLocalRepo {
pub repo: String,
pub hooks: Vec<ConfigLocalHook>,
}

impl Display for ConfigLocalRepo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("local")
}
}

#[derive(Debug, Clone)]
pub struct ConfigMetaRepo {
pub repo: String,
pub hooks: Vec<ConfigMetaHook>,
}

impl Display for ConfigMetaRepo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("meta")
}
}

#[derive(Debug, Clone)]
pub enum ConfigRepo {
Remote(ConfigRemoteRepo),
Expand Down
Loading

0 comments on commit d244af4

Please sign in to comment.