From 326169a7fabacda9a21377b110371f91b32e8fd3 Mon Sep 17 00:00:00 2001
From: mo8it <mo8it@proton.me>
Date: Sun, 13 Oct 2024 22:02:41 +0200
Subject: [PATCH] Improve check-all command

---
 clippy.toml      |  2 ++
 src/app_state.rs |  5 +++++
 src/main.rs      | 54 ++++++++++++++++++++++++------------------------
 src/run.rs       |  8 +++----
 4 files changed, 38 insertions(+), 31 deletions(-)

diff --git a/clippy.toml b/clippy.toml
index 4a5dd06..11ec6cc 100644
--- a/clippy.toml
+++ b/clippy.toml
@@ -13,4 +13,6 @@ disallowed-methods = [
   # Use `thread::Builder::spawn` instead and handle the error.
   "std::thread::spawn",
   "std::thread::Scope::spawn",
+  # Return `ExitCode` instead.
+  "std::process::exit",
 ]
diff --git a/src/app_state.rs b/src/app_state.rs
index 7540181..c399842 100644
--- a/src/app_state.rs
+++ b/src/app_state.rs
@@ -211,6 +211,11 @@ impl AppState {
         self.n_done
     }
 
+    #[inline]
+    pub fn n_pending(&self) -> u16 {
+        self.exercises.len() as u16 - self.n_done
+    }
+
     #[inline]
     pub fn current_exercise(&self) -> &Exercise {
         &self.exercises[self.current_exercise_ind]
diff --git a/src/main.rs b/src/main.rs
index 64b72bd..f40bb89 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,7 +8,7 @@ use crossterm::{
 use std::{
     io::{self, IsTerminal, Write},
     path::Path,
-    process::exit,
+    process::ExitCode,
 };
 use term::{clear_terminal, press_enter_prompt};
 
@@ -51,8 +51,8 @@ enum Subcommands {
         /// The name of the exercise
         name: Option<String>,
     },
-    /// Run all the exercises, marking them as done or pending accordingly.
-    RunAll,
+    /// Check all the exercises, marking them as done or pending accordingly.
+    CheckAll,
     /// Reset a single exercise
     Reset {
         /// The name of the exercise
@@ -68,22 +68,26 @@ enum Subcommands {
     Dev(DevCommands),
 }
 
-fn main() -> Result<()> {
+fn main() -> Result<ExitCode> {
     let args = Args::parse();
 
     if cfg!(not(debug_assertions)) && Path::new("dev/rustlings-repo.txt").exists() {
         bail!("{OLD_METHOD_ERR}");
     }
 
-    match args.command {
-        Some(Subcommands::Init) => return init::init().context("Initialization failed"),
-        Some(Subcommands::Dev(dev_command)) => return dev_command.run(),
-        _ => (),
+    'priority_cmd: {
+        match args.command {
+            Some(Subcommands::Init) => init::init().context("Initialization failed")?,
+            Some(Subcommands::Dev(dev_command)) => dev_command.run()?,
+            _ => break 'priority_cmd,
+        }
+
+        return Ok(ExitCode::SUCCESS);
     }
 
     if !Path::new("exercises").is_dir() {
         println!("{PRE_INIT_MSG}");
-        exit(1);
+        return Ok(ExitCode::FAILURE);
     }
 
     let info_file = InfoFile::parse()?;
@@ -142,33 +146,29 @@ fn main() -> Result<()> {
             if let Some(name) = name {
                 app_state.set_current_exercise_by_name(&name)?;
             }
-            run::run(&mut app_state)?;
+            return run::run(&mut app_state);
         }
-        Some(Subcommands::RunAll) => {
+        Some(Subcommands::CheckAll) => {
             let mut stdout = io::stdout().lock();
-            if let Some(first_fail) = app_state.check_all_exercises(&mut stdout)? {
-                let pending = app_state
-                    .exercises()
-                    .iter()
-                    .filter(|exercise| !exercise.done)
-                    .count();
+            if let Some(first_pending_exercise_ind) = app_state.check_all_exercises(&mut stdout)? {
                 if app_state.current_exercise().done {
-                    app_state.set_current_exercise_ind(first_fail)?;
+                    app_state.set_current_exercise_ind(first_pending_exercise_ind)?;
                 }
-                stdout
-                    .queue(SetForegroundColor(Color::Red))?
-                    .queue(Print(format!("{pending}")))?
-                    .queue(ResetColor)?;
+
+                let pending = app_state.n_pending();
                 if pending == 1 {
-                    stdout.queue(Print(" exercise has some errors: "))?;
+                    stdout.queue(Print("One exercise pending: "))?;
                 } else {
-                    stdout.queue(Print(" exercises have errors, including "))?;
+                    stdout.queue(SetForegroundColor(Color::Red))?;
+                    write!(stdout, "{pending}")?;
+                    stdout.queue(ResetColor)?;
+                    stdout.queue(Print(" exercises are pending. The first: "))?;
                 }
                 app_state
                     .current_exercise()
                     .terminal_file_link(&mut stdout)?;
-                stdout.write_all(b".\n")?;
-                exit(1);
+                stdout.write_all(b"\n")?;
+                return Ok(ExitCode::FAILURE);
             } else {
                 app_state.render_final_message(&mut stdout)?;
             }
@@ -188,7 +188,7 @@ fn main() -> Result<()> {
         Some(Subcommands::Init | Subcommands::Dev(_)) => (),
     }
 
-    Ok(())
+    Ok(ExitCode::SUCCESS)
 }
 
 const OLD_METHOD_ERR: &str =
diff --git a/src/run.rs b/src/run.rs
index 3fddcf2..f259f52 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -5,7 +5,7 @@ use crossterm::{
 };
 use std::{
     io::{self, Write},
-    process::exit,
+    process::ExitCode,
 };
 
 use crate::{
@@ -13,7 +13,7 @@ use crate::{
     exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
 };
 
-pub fn run(app_state: &mut AppState) -> Result<()> {
+pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
     let exercise = app_state.current_exercise();
     let mut output = Vec::with_capacity(OUTPUT_CAPACITY);
     let success = exercise.run_exercise(Some(&mut output), app_state.cmd_runner())?;
@@ -29,7 +29,7 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
             .current_exercise()
             .terminal_file_link(&mut stdout)?;
         stdout.write_all(b" with errors\n")?;
-        exit(1);
+        return Ok(ExitCode::FAILURE);
     }
 
     stdout.queue(SetForegroundColor(Color::Green))?;
@@ -55,5 +55,5 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
         ExercisesProgress::AllDone => (),
     }
 
-    Ok(())
+    Ok(ExitCode::SUCCESS)
 }