repo_description: :crab: Small exercises to get you used to reading and writing Rust code!
repo_topics: beginner-friendly exercises rust rustlings
code: use anyhow::Result;
  use crossterm::{
      QueueableCommand,
      style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
  };
  use std::io::{self, StdoutLock, Write};
  
  use crate::{
      cmd::CmdRunner,
      term::{self, CountedWrite, file_path, terminal_file_link, write_ansi},
  };
  
  /// The initial capacity of the output buffer.
  pub const OUTPUT_CAPACITY: usize = 1 << 14;
  
  pub fn solution_link_line(
      stdout: &mut StdoutLock,
      solution_path: &str,
      emit_file_links: bool,
  ) -> io::Result<()> {
      stdout.queue(SetAttribute(Attribute::Bold))?;
      stdout.write_all(b"Solution")?;
      stdout.queue(ResetColor)?;
      stdout.write_all(b" for comparison: ")?;
      file_path(stdout, Color::Cyan, |writer| {
          if emit_file_links && let Some(canonical_path) = term::canonicalize(solution_path) {
              terminal_file_link(writer, solution_path, &canonical_path)
          } else {
              writer.stdout().write_all(solution_path.as_bytes())
          }
      })?;
      stdout.write_all(b"\n")
  }
  
  // Run an exercise binary and append its output to the `output` buffer.
  // Compilation must be done before calling this method.
  fn run_bin(
      bin_name: &str,
      mut output: Option<&mut Vec<u8>>,
      cmd_runner: &CmdRunner,
  ) -> Result<bool> {
      if let Some(output) = output.as_deref_mut() {
          write_ansi(output, SetAttribute(Attribute::Underlined));
          output.extend_from_slice(b"Output");
          write_ansi(output, ResetColor);
          output.push(b'\n');
      }
  
      let success = cmd_runner.run_debug_bin(bin_name, output.as_deref_mut())?;
  
      if let Some(output) = output
          && !success
      {
          // This output is important to show the user that something went wrong.
          // Otherwise, calling something like `exit(1)` in an exercise without further output
          // leaves the user confused about why the exercise isn't done yet.
          write_ansi(output, SetAttribute(Attribute::Bold));
          write_ansi(output, SetForegroundColor(Color::Red));
          output.extend_from_slice(b"The exercise didn't run successfully (nonzero exit code)");
          write_ansi(output, ResetColor);
          output.push(b'\n');
      }
  
      Ok(success)
  }
  
  /// See `info_file::ExerciseInfo`
  pub struct Exercise {
      pub name: &'static str,
      pub dir: Option<&'static str>,
      /// Path of the exercise file starting with the `exercises/` directory.
      pub path: &'static str,
      pub canonical_path: Option<String>,
      pub test: bool,
      pub strict_clippy: bool,
      pub hint: &'static str,
      pub done: bool,
  }
  
  impl Exercise {
      pub fn terminal_file_link<'a>(
          &self,
          writer: &mut impl CountedWrite<'a>,
          emit_file_links: bool,
      ) -> io::Result<()> {
          file_path(writer, Color::Blue, |writer| {
              if emit_file_links && let Some(canonical_path) = self.canonical_path.as_deref() {
                  terminal_file_link(writer, self.path, canonical_path)
              } else {
                  writer.write_str(self.path)
              }
          })
      }
  }
  
  pub trait RunnableExercise {
      fn name(&self) -> &str;
      fn dir(&self) -> Option<&str>;
      fn strict_clippy(&self) -> bool;
      fn test(&self) -> bool;
  
      // Compile, check and run the exercise or its solution (depending on `bin_name´).
      // The output is written to the `output` buffer after clearing it.
      fn run<const FORCE_STRICT_CLIPPY: bool>(
          &self,
          bin_name: &str,
          mut output: Option<&mut Vec<u8>>,
          cmd_runner: &CmdRunner,
      ) -> Result<bool> {
          if let Some(output) = output.as_deref_mut() {
              output.clear();
          }
  
          let build_success = cmd_runner
              .cargo("build", bin_name, output.as_deref_mut())
              .run("cargo build …")?;
          if !build_success {
              return Ok(false);
          }
  
          // Discard the compiler output because it will be shown again by `cargo test` or Clippy.
          if let Some(output) = output.as_deref_mut() {
              output.clear();
          }
  
          if self.test() {
              let output_is_some = output.is_some();
              let mut test_cmd = cmd_runner.cargo("test", bin_name, output.as_deref_mut());
              if output_is_some {
                  test_cmd.args(["--", "--color", "always", "--format", "pretty"]);
              }
              let test_success = test_cmd.run("cargo test …")?;
              if !test_success {
                  run_bin(bin_name, output, cmd_runner)?;
                  return Ok(false);
              }
  
              // Discard the compiler output because it will be shown again by Clippy.
              if let Some(output) = output.as_deref_mut() {
                  output.clear();
              }
          }
  
          let mut clippy_cmd = cmd_runner.cargo("clippy", bin_name, output.as_deref_mut());
  
          // `--profile test` is required to also check code with `#[cfg(test)]`.
          if FORCE_STRICT_CLIPPY || self.strict_clippy() {
              clippy_cmd.args(["--profile", "test", "--", "-D", "warnings"]);
          } else {
              clippy_cmd.args(["--profile", "test"]);
          }
  
          let clippy_success = clippy_cmd.run("cargo clippy …")?;
          let run_success = run_bin(bin_name, output, cmd_runner)?;
  
          Ok(clippy_success && run_success)
      }
  
      /// Compile, check and run the exercise.
      /// The output is written to the `output` buffer after clearing it.
      fn run_exercise(&self, output: Option<&mut Vec<u8>>, cmd_runner: &CmdRunner) -> Result<bool> {
          self.run::<false>(self.name(), output, cmd_runner)
      }
  
      /// Compile, check and run the exercise's solution.
      /// The output is written to the `output` buffer after clearing it.
      fn run_solution(&self, output: Option<&mut Vec<u8>>, cmd_runner: &CmdRunner) -> Result<bool> {
          let name = self.name();
          let mut bin_name = String::with_capacity(name.len() + 4);
          bin_name.push_str(name);
          bin_name.push_str("_sol");
  
          self.run::<true>(&bin_name, output, cmd_runner)
      }
  
      fn sol_path(&self) -> String {
          let name = self.name();
  
          let mut path = if let Some(dir) = self.dir() {
              // 14 = 10 + 1 + 3
              // solutions/ + / + .rs
              let mut path = String::with_capacity(14 + dir.len() + name.len());
              path.push_str("solutions/");
              path.push_str(dir);
              path.push('/');
              path
          } else {
              // 13 = 10 + 3
              // solutions/ + .rs
              let mut path = String::with_capacity(13 + name.len());
              path.push_str("solutions/");
              path
          };
  
          path.push_str(name);
          path.push_str(".rs");
  
          path
      }
  }
  
  impl RunnableExercise for Exercise {
      fn name(&self) -> &str {
          self.name
      }
  
      fn dir(&self) -> Option<&str> {
          self.dir
      }
  
      fn strict_clippy(&self) -> bool {
          self.strict_clippy
      }
  
      fn test(&self) -> bool {
          self.test
      }
  }
  