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::{Context, Error, Result, bail};
  use serde::Deserialize;
  use std::{fs, io::ErrorKind};
  
  use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise};
  
  /// Deserialized from the `info.toml` file.
  #[derive(Deserialize)]
  pub struct ExerciseInfo {
      /// Exercise's unique name.
      pub name: &'static str,
      /// Exercise's directory name inside the `exercises/` directory.
      pub dir: Option<&'static str>,
      /// Run `cargo test` on the exercise.
      #[serde(default = "default_true")]
      pub test: bool,
      /// Deny all Clippy warnings.
      #[serde(default)]
      pub strict_clippy: bool,
      /// The exercise's hint to be shown to the user on request.
      pub hint: &'static str,
      /// The exercise is already solved. Ignore it when checking that all exercises are unsolved.
      #[serde(default)]
      pub skip_check_unsolved: bool,
  }
  const fn default_true() -> bool {
      true
  }
  
  impl ExerciseInfo {
      /// Path to the exercise file starting with the `exercises/` directory.
      pub fn path(&self) -> String {
          let mut path = if let Some(dir) = self.dir {
              // 14 = 10 + 1 + 3
              // exercises/ + / + .rs
              let mut path = String::with_capacity(14 + dir.len() + self.name.len());
              path.push_str("exercises/");
              path.push_str(dir);
              path.push('/');
              path
          } else {
              // 13 = 10 + 3
              // exercises/ + .rs
              let mut path = String::with_capacity(13 + self.name.len());
              path.push_str("exercises/");
              path
          };
  
          path.push_str(self.name);
          path.push_str(".rs");
  
          path
      }
  }
  
  impl RunnableExercise for ExerciseInfo {
      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
      }
  }
  
  /// The deserialized `info.toml` file.
  #[derive(Deserialize)]
  pub struct InfoFile {
      /// For possible breaking changes in the future for community exercises.
      pub format_version: u8,
      /// Shown to users when starting with the exercises.
      pub welcome_message: Option<&'static str>,
      /// Shown to users after finishing all exercises.
      pub final_message: Option<&'static str>,
      /// List of all exercises.
      pub exercises: Vec<ExerciseInfo>,
  }
  
  impl InfoFile {
      /// Official exercises: Parse the embedded `info.toml` file.
      /// Community exercises: Parse the `info.toml` file in the current directory.
      pub fn parse() -> Result<Self> {
          // Read a local `info.toml` if it exists.
          let slf = match fs::read("info.toml") {
              Ok(file_content) => {
                  // Remove `\r` on Windows.
                  // Leaking is fine since the info file is used until the end of the program.
                  let file_content =
                      String::from_utf8(file_content.into_iter().filter(|c| *c != b'\r').collect())
                          .context("Failed to parse `info.toml` as UTF8")?
                          .leak();
                  toml::de::from_str::<Self>(file_content)
                      .context("Failed to parse the `info.toml` file")?
              }
              Err(e) => {
                  if e.kind() == ErrorKind::NotFound {
                      return toml::de::from_str(EMBEDDED_FILES.info_file)
                          .context("Failed to parse the embedded `info.toml` file");
                  }
  
                  return Err(Error::from(e).context("Failed to read the `info.toml` file"));
              }
          };
  
          if slf.exercises.is_empty() {
              bail!("{NO_EXERCISES_ERR}");
          }
  
          Ok(slf)
      }
  }
  
  const NO_EXERCISES_ERR: &str = "There are no exercises yet!
  Add at least one exercise before testing.";
  