From 6618cf2d685dbf2255c7357ba90326baba70cb1f Mon Sep 17 00:00:00 2001
From: Poliorcetics <poliorcetics@users.noreply.github.com>
Date: Wed, 17 Aug 2022 03:37:10 +0200
Subject: [PATCH] feat: shorten output for grammar fetching and building
 (#3396)

New look:

```
Fetching 102 grammars
98 up to date git grammars
4 updated grammars
        bash      now on 275effdfc0edce774acf7d481f9ea195c6c403cd
        beancount now on 4cbd1f09cd07c1f1fabf867c2cf354f9da53cc4c
        c         now on f05e279aedde06a25801c3f2b2cc8ac17fac52ae
        c-sharp   now on 53a65a908167d6556e1fcdb67f1ee62aac101dda
```

```
Building 102 grammars
100 grammars already built
2 grammars built now
        ["bash", "rust"]
```
---
 helix-loader/src/grammar.rs | 157 +++++++++++++++++++++++++++++-------
 1 file changed, 129 insertions(+), 28 deletions(-)

diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs
index 231ecf34..98a93e56 100644
--- a/helix-loader/src/grammar.rs
+++ b/helix-loader/src/grammar.rs
@@ -89,15 +89,102 @@ pub fn fetch_grammars() -> Result<()> {
     let mut grammars = get_grammar_configs()?;
     grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
 
-    run_parallel(grammars, fetch_grammar, "fetch")
+    println!("Fetching {} grammars", grammars.len());
+    let results = run_parallel(grammars, fetch_grammar);
+
+    let mut errors = Vec::new();
+    let mut git_updated = Vec::new();
+    let mut git_up_to_date = 0;
+    let mut non_git = Vec::new();
+
+    for res in results {
+        match res {
+            Ok(FetchStatus::GitUpToDate) => git_up_to_date += 1,
+            Ok(FetchStatus::GitUpdated {
+                grammar_id,
+                revision,
+            }) => git_updated.push((grammar_id, revision)),
+            Ok(FetchStatus::NonGit { grammar_id }) => non_git.push(grammar_id),
+            Err(e) => errors.push(e),
+        }
+    }
+
+    non_git.sort_unstable();
+    git_updated.sort_unstable_by(|a, b| a.0.cmp(&b.0));
+
+    if git_up_to_date != 0 {
+        println!("{} up to date git grammars", git_up_to_date);
+    }
+
+    if !non_git.is_empty() {
+        println!("{} non git grammars", non_git.len());
+        println!("\t{:?}", non_git);
+    }
+
+    if !git_updated.is_empty() {
+        println!("{} updated grammars", git_updated.len());
+        // We checked the vec is not empty, unwrapping will not panic
+        let longest_id = git_updated.iter().map(|x| x.0.len()).max().unwrap();
+        for (id, rev) in git_updated {
+            println!(
+                "\t{id:width$} now on {rev}",
+                id = id,
+                width = longest_id,
+                rev = rev
+            );
+        }
+    }
+
+    if !errors.is_empty() {
+        let len = errors.len();
+        println!("{} grammars failed to fetch", len);
+        for (i, error) in errors.into_iter().enumerate() {
+            println!("\tFailure {}/{}: {}", i, len, error);
+        }
+    }
+
+    Ok(())
 }
 
 pub fn build_grammars(target: Option<String>) -> Result<()> {
-    run_parallel(
-        get_grammar_configs()?,
-        move |grammar| build_grammar(grammar, target.as_deref()),
-        "build",
-    )
+    let grammars = get_grammar_configs()?;
+    println!("Building {} grammars", grammars.len());
+    let results = run_parallel(grammars, move |grammar| {
+        build_grammar(grammar, target.as_deref())
+    });
+
+    let mut errors = Vec::new();
+    let mut already_built = 0;
+    let mut built = Vec::new();
+
+    for res in results {
+        match res {
+            Ok(BuildStatus::AlreadyBuilt) => already_built += 1,
+            Ok(BuildStatus::Built { grammar_id }) => built.push(grammar_id),
+            Err(e) => errors.push(e),
+        }
+    }
+
+    built.sort_unstable();
+
+    if already_built != 0 {
+        println!("{} grammars already built", already_built);
+    }
+
+    if !built.is_empty() {
+        println!("{} grammars built now", built.len());
+        println!("\t{:?}", built);
+    }
+
+    if !errors.is_empty() {
+        let len = errors.len();
+        println!("{} grammars failed to build", len);
+        for (i, error) in errors.into_iter().enumerate() {
+            println!("\tFailure {}/{}: {}", i, len, error);
+        }
+    }
+
+    Ok(())
 }
 
 // Returns the set of grammar configurations the user requests.
@@ -126,9 +213,10 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
     Ok(grammars)
 }
 
-fn run_parallel<F>(grammars: Vec<GrammarConfiguration>, job: F, action: &'static str) -> Result<()>
+fn run_parallel<F, Res>(grammars: Vec<GrammarConfiguration>, job: F) -> Vec<Result<Res>>
 where
-    F: Fn(GrammarConfiguration) -> Result<()> + std::marker::Send + 'static + Clone,
+    F: Fn(GrammarConfiguration) -> Result<Res> + Send + 'static + Clone,
+    Res: Send + 'static,
 {
     let pool = threadpool::Builder::new().build();
     let (tx, rx) = channel();
@@ -146,14 +234,21 @@ where
 
     drop(tx);
 
-    // TODO: print all failures instead of the first one found.
-    rx.iter()
-        .find(|result| result.is_err())
-        .map(|err| err.with_context(|| format!("Failed to {} some grammar(s)", action)))
-        .unwrap_or(Ok(()))
+    rx.iter().collect()
 }
 
-fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> {
+enum FetchStatus {
+    GitUpToDate,
+    GitUpdated {
+        grammar_id: String,
+        revision: String,
+    },
+    NonGit {
+        grammar_id: String,
+    },
+}
+
+fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
     if let GrammarSource::Git {
         remote, revision, ..
     } = grammar.source
@@ -189,16 +284,18 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> {
             )?;
             git(&grammar_dir, ["checkout", &revision])?;
 
-            println!(
-                "Grammar '{}' checked out at '{}'.",
-                grammar.grammar_id, revision
-            );
+            Ok(FetchStatus::GitUpdated {
+                grammar_id: grammar.grammar_id,
+                revision,
+            })
         } else {
-            println!("Grammar '{}' is already up to date.", grammar.grammar_id);
+            Ok(FetchStatus::GitUpToDate)
         }
+    } else {
+        Ok(FetchStatus::NonGit {
+            grammar_id: grammar.grammar_id,
+        })
     }
-
-    Ok(())
 }
 
 // Sets the remote for a repository to the given URL, creating the remote if
@@ -245,7 +342,12 @@ where
     }
 }
 
-fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result<()> {
+enum BuildStatus {
+    AlreadyBuilt,
+    Built { grammar_id: String },
+}
+
+fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result<BuildStatus> {
     let grammar_dir = if let GrammarSource::Local { path } = &grammar.source {
         PathBuf::from(&path)
     } else {
@@ -285,7 +387,7 @@ fn build_tree_sitter_library(
     src_path: &Path,
     grammar: GrammarConfiguration,
     target: Option<&str>,
-) -> Result<()> {
+) -> Result<BuildStatus> {
     let header_path = src_path;
     let parser_path = src_path.join("parser.c");
     let mut scanner_path = src_path.join("scanner.c");
@@ -308,12 +410,9 @@ fn build_tree_sitter_library(
         .context("Failed to compare source and binary timestamps")?;
 
     if !recompile {
-        println!("Grammar '{}' is already built.", grammar.grammar_id);
-        return Ok(());
+        return Ok(BuildStatus::AlreadyBuilt);
     }
 
-    println!("Building grammar '{}'", grammar.grammar_id);
-
     let mut config = cc::Build::new();
     config
         .cpp(true)
@@ -381,7 +480,9 @@ fn build_tree_sitter_library(
         ));
     }
 
-    Ok(())
+    Ok(BuildStatus::Built {
+        grammar_id: grammar.grammar_id,
+    })
 }
 
 fn needs_recompile(