Compare commits

..

No commits in common. "01e6732e4d920d9a1859e05fa28382e4307571af" and "634e17a5abdd5b03740cfb5ab690e2b8762cf0c3" have entirely different histories.

9 changed files with 61 additions and 113 deletions

View file

@ -7,15 +7,9 @@ use crossterm::{
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::{Read, StdoutLock, Write}, io::{Read, StdoutLock, Write},
path::Path,
process::{Command, Stdio},
}; };
use crate::{ use crate::{exercise::Exercise, info_file::ExerciseInfo, FENISH_LINE};
embedded::{WriteStrategy, EMBEDDED_FILES},
exercise::Exercise,
info_file::ExerciseInfo,
};
const STATE_FILE_NAME: &str = ".rustlings-state.txt"; const STATE_FILE_NAME: &str = ".rustlings-state.txt";
const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises"; const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises";
@ -37,7 +31,6 @@ pub struct AppState {
n_done: u16, n_done: u16,
final_message: String, final_message: String,
file_buf: Vec<u8>, file_buf: Vec<u8>,
official_exercises: bool,
} }
impl AppState { impl AppState {
@ -118,7 +111,6 @@ impl AppState {
n_done: 0, n_done: 0,
final_message, final_message,
file_buf: Vec::with_capacity(2048), file_buf: Vec::with_capacity(2048),
official_exercises: !Path::new("info.toml").exists(),
}; };
let state_file_status = slf.update_from_file(); let state_file_status = slf.update_from_file();
@ -180,53 +172,6 @@ impl AppState {
Ok(()) Ok(())
} }
fn reset_path(&self, path: &str) -> Result<()> {
if self.official_exercises {
return EMBEDDED_FILES
.write_exercise_to_disk(path, WriteStrategy::Overwrite)
.with_context(|| format!("Failed to reset the exercise {path}"));
}
let output = Command::new("git")
.arg("stash")
.arg("push")
.arg("--")
.arg(path)
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
.with_context(|| format!("Failed to run `git stash push -- {path}`"))?;
if !output.status.success() {
bail!(
"`git stash push -- {path}` didn't run successfully: {}",
String::from_utf8_lossy(&output.stderr),
);
}
Ok(())
}
pub fn reset_current_exercise(&mut self) -> Result<&'static str> {
let path = self.current_exercise().path;
self.set_pending(self.current_exercise_ind)?;
self.reset_path(path)?;
Ok(path)
}
pub fn reset_exercise_by_ind(&mut self, exercise_ind: usize) -> Result<&'static str> {
if exercise_ind >= self.exercises.len() {
bail!(BAD_INDEX_ERR);
}
let path = self.exercises[exercise_ind].path;
self.set_pending(exercise_ind)?;
self.reset_path(path)?;
Ok(path)
}
fn next_pending_exercise_ind(&self) -> Option<usize> { fn next_pending_exercise_ind(&self) -> Option<usize> {
if self.current_exercise_ind == self.exercises.len() - 1 { if self.current_exercise_ind == self.exercises.len() - 1 {
// The last exercise is done. // The last exercise is done.
@ -330,25 +275,3 @@ All exercises seem to be done.
Recompiling and running all exercises to make sure that all of them are actually done. Recompiling and running all exercises to make sure that all of them are actually done.
"; ";
const FENISH_LINE: &str = "+----------------------------------------------------+
| You made it to the Fe-nish line! |
+-------------------------- ------------------------+
\\/\x1b[31m
\x1b[0m
";

View file

@ -1,8 +1,6 @@
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result};
use clap::Subcommand; use clap::Subcommand;
use crate::DEVELOPING_OFFICIAL_RUSTLINGS;
mod check; mod check;
mod init; mod init;
mod update; mod update;
@ -17,13 +15,7 @@ pub enum DevCommands {
impl DevCommands { impl DevCommands {
pub fn run(self) -> Result<()> { pub fn run(self) -> Result<()> {
match self { match self {
DevCommands::Init => { DevCommands::Init => init::init().context(INIT_ERR),
if DEVELOPING_OFFICIAL_RUSTLINGS {
bail!("Disabled while developing the official Rustlings");
}
init::init().context(INIT_ERR)
}
DevCommands::Check => check::check(), DevCommands::Check => check::check(),
DevCommands::Update => update::update(), DevCommands::Update => update::update(),
} }

View file

@ -79,7 +79,7 @@ fn unexpected_file(path: &Path) -> Error {
anyhow!("Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `exercises` directory", path.display()) anyhow!("Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `exercises` directory", path.display())
} }
fn check_exercise_dir_files(info_file_paths: &hashbrown::HashSet<PathBuf>) -> Result<()> { fn check_exercise_dir_files(info_file_paths: hashbrown::HashSet<PathBuf>) -> Result<()> {
for entry in read_dir("exercises").context("Failed to open the `exercises` directory")? { for entry in read_dir("exercises").context("Failed to open the `exercises` directory")? {
let entry = entry.context("Failed to read the `exercises` directory")?; let entry = entry.context("Failed to read the `exercises` directory")?;
@ -131,7 +131,7 @@ fn check_exercises(info_file: &InfoFile) -> Result<()> {
} }
let info_file_paths = check_info_file_exercises(info_file)?; let info_file_paths = check_info_file_exercises(info_file)?;
check_exercise_dir_files(&info_file_paths)?; check_exercise_dir_files(info_file_paths)?;
Ok(()) Ok(())
} }

View file

@ -47,12 +47,14 @@ impl EmbeddedFlatDir {
let path = Path::new(self.path); let path = Path::new(self.path);
if let Err(e) = create_dir(path) { if let Err(e) = create_dir(path) {
if e.kind() != io::ErrorKind::AlreadyExists { if !path.is_dir() {
return Err(e); return Err(e);
} }
} }
self.readme.write_to_disk(WriteStrategy::Overwrite) self.readme.write_to_disk(WriteStrategy::Overwrite)?;
Ok(())
} }
} }

View file

@ -7,7 +7,11 @@ use std::{
process::{Command, Output}, process::{Command, Output},
}; };
use crate::{info_file::Mode, DEVELOPING_OFFICIAL_RUSTLINGS}; use crate::{
embedded::{WriteStrategy, EMBEDDED_FILES},
info_file::Mode,
DEVELOPING_OFFICIAL_RUSTLINGS,
};
pub struct TerminalFileLink<'a> { pub struct TerminalFileLink<'a> {
path: &'a str, path: &'a str,
@ -83,6 +87,12 @@ impl Exercise {
} }
} }
pub fn reset(&self) -> Result<()> {
EMBEDDED_FILES
.write_exercise_to_disk(self.path, WriteStrategy::Overwrite)
.with_context(|| format!("Failed to reset the exercise {self}"))
}
pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> { pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> {
style(TerminalFileLink { path: self.path }) style(TerminalFileLink { path: self.path })
.underlined() .underlined()

View file

@ -7,12 +7,12 @@ use crossterm::{
use ratatui::{backend::CrosstermBackend, Terminal}; use ratatui::{backend::CrosstermBackend, Terminal};
use std::io; use std::io;
mod state;
use crate::app_state::AppState; use crate::app_state::AppState;
use self::state::{Filter, UiState}; use self::state::{Filter, UiState};
mod state;
pub fn list(app_state: &mut AppState) -> Result<()> { pub fn list(app_state: &mut AppState) -> Result<()> {
let mut stdout = io::stdout().lock(); let mut stdout = io::stdout().lock();
stdout.execute(EnterAlternateScreen)?; stdout.execute(EnterAlternateScreen)?;

View file

@ -217,22 +217,23 @@ impl<'a> UiState<'a> {
return Ok(self); return Ok(self);
}; };
let ind = self let (ind, exercise) = self
.app_state .app_state
.exercises() .exercises()
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(ind, exercise)| match self.filter { .filter_map(|(ind, exercise)| match self.filter {
Filter::Done => exercise.done.then_some(ind), Filter::Done => exercise.done.then_some((ind, exercise)),
Filter::Pending => (!exercise.done).then_some(ind), Filter::Pending => (!exercise.done).then_some((ind, exercise)),
Filter::None => Some(ind), Filter::None => Some((ind, exercise)),
}) })
.nth(selected) .nth(selected)
.context("Invalid selection index")?; .context("Invalid selection index")?;
let exercise_path = self.app_state.reset_exercise_by_ind(ind)?; exercise.reset()?;
self.message self.message
.write_fmt(format_args!("The exercise {exercise_path} has been reset"))?; .write_fmt(format_args!("The exercise {exercise} has been reset!"))?;
self.app_state.set_pending(ind)?;
Ok(self.with_updated_rows()) Ok(self.with_updated_rows())
} }

View file

@ -11,8 +11,6 @@ use std::{
process::exit, process::exit,
}; };
use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::WatchExit};
mod app_state; mod app_state;
mod dev; mod dev;
mod embedded; mod embedded;
@ -24,6 +22,8 @@ mod progress_bar;
mod run; mod run;
mod watch; mod watch;
use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::WatchExit};
const CURRENT_FORMAT_VERSION: u8 = 1; const CURRENT_FORMAT_VERSION: u8 = 1;
const DEVELOPING_OFFICIAL_RUSTLINGS: bool = { const DEVELOPING_OFFICIAL_RUSTLINGS: bool = {
#[allow(unused_assignments, unused_mut)] #[allow(unused_assignments, unused_mut)]
@ -79,10 +79,6 @@ fn main() -> Result<()> {
match args.command { match args.command {
Some(Subcommands::Init) => { Some(Subcommands::Init) => {
if DEVELOPING_OFFICIAL_RUSTLINGS {
bail!("Disabled while developing the official Rustlings");
}
return init::init().context("Initialization failed"); return init::init().context("Initialization failed");
} }
Some(Subcommands::Dev(dev_command)) => return dev_command.run(), Some(Subcommands::Dev(dev_command)) => return dev_command.run(),
@ -158,8 +154,10 @@ fn main() -> Result<()> {
} }
Some(Subcommands::Reset { name }) => { Some(Subcommands::Reset { name }) => {
app_state.set_current_exercise_by_name(&name)?; app_state.set_current_exercise_by_name(&name)?;
let exercise_path = app_state.reset_current_exercise()?; let exercise = app_state.current_exercise();
println!("The exercise {exercise_path} has been reset"); exercise.reset()?;
println!("The exercise {exercise} has been reset!");
app_state.set_pending(app_state.current_exercise_ind())?;
} }
Some(Subcommands::Hint { name }) => { Some(Subcommands::Hint { name }) => {
app_state.set_current_exercise_by_name(&name)?; app_state.set_current_exercise_by_name(&name)?;
@ -192,3 +190,25 @@ const PRE_INIT_MSG: &str = r"
The `exercises` directory wasn't found in the current directory. The `exercises` directory wasn't found in the current directory.
If you are just starting with Rustlings, run the command `rustlings init` to initialize it."; If you are just starting with Rustlings, run the command `rustlings init` to initialize it.";
const FENISH_LINE: &str = "+----------------------------------------------------+
| You made it to the Fe-nish line! |
+-------------------------- ------------------------+
\\/\x1b[31m
\x1b[0m
";

View file

@ -11,6 +11,10 @@ use std::{
time::Duration, time::Duration,
}; };
mod notify_event;
mod state;
mod terminal_event;
use crate::app_state::{AppState, ExercisesProgress}; use crate::app_state::{AppState, ExercisesProgress};
use self::{ use self::{
@ -19,10 +23,6 @@ use self::{
terminal_event::{terminal_event_handler, InputEvent}, terminal_event::{terminal_event_handler, InputEvent},
}; };
mod notify_event;
mod state;
mod terminal_event;
enum WatchEvent { enum WatchEvent {
Input(InputEvent), Input(InputEvent),
FileChange { exercise_ind: usize }, FileChange { exercise_ind: usize },