Some improvements to error handling

This commit is contained in:
mo8it 2024-03-31 18:25:54 +02:00
parent 82b563f165
commit c1de4d46aa
3 changed files with 55 additions and 66 deletions

View file

@ -114,14 +114,9 @@ impl Exercise {
} }
} }
pub fn state(&self) -> State { pub fn state(&self) -> Result<State> {
let source_file = File::open(&self.path).unwrap_or_else(|e| { let source_file = File::open(&self.path)
println!( .with_context(|| format!("Failed to open the exercise file {}", self.path.display()))?;
"Failed to open the exercise file {}: {e}",
self.path.display(),
);
exit(1);
});
let mut source_reader = BufReader::new(source_file); let mut source_reader = BufReader::new(source_file);
// Read the next line into `buf` without the newline at the end. // Read the next line into `buf` without the newline at the end.
@ -152,7 +147,7 @@ impl Exercise {
// Reached the end of the file and didn't find the comment. // Reached the end of the file and didn't find the comment.
if n == 0 { if n == 0 {
return State::Done; return Ok(State::Done);
} }
if contains_not_done_comment(&line) { if contains_not_done_comment(&line) {
@ -198,7 +193,7 @@ impl Exercise {
}); });
} }
return State::Pending(context); return Ok(State::Pending(context));
} }
current_line_number += 1; current_line_number += 1;
@ -218,8 +213,8 @@ impl Exercise {
// without actually having solved anything. // without actually having solved anything.
// The only other way to truly check this would to compile and run // The only other way to truly check this would to compile and run
// the exercise; which would be both costly and counterintuitive // the exercise; which would be both costly and counterintuitive
pub fn looks_done(&self) -> bool { pub fn looks_done(&self) -> Result<bool> {
self.state() == State::Done self.state().map(|state| state == State::Done)
} }
} }
@ -271,7 +266,7 @@ mod test {
}, },
]; ];
assert_eq!(state, State::Pending(expected)); assert_eq!(state.unwrap(), State::Pending(expected));
} }
#[test] #[test]
@ -283,7 +278,7 @@ mod test {
hint: String::new(), hint: String::new(),
}; };
assert_eq!(exercise.state(), State::Done); assert_eq!(exercise.state().unwrap(), State::Done);
} }
#[test] #[test]

View file

@ -92,14 +92,11 @@ fn main() -> Result<()> {
println!("\n{WELCOME}\n"); println!("\n{WELCOME}\n");
} }
if which::which("cargo").is_err() { which::which("cargo").context(
println!( "Failed to find `cargo`.
"Failed to find `cargo`.
Did you already install Rust? Did you already install Rust?
Try running `cargo --version` to diagnose the problem." Try running `cargo --version` to diagnose the problem.",
); )?;
std::process::exit(1);
}
let exercises = ExerciseList::parse()?.exercises; let exercises = ExerciseList::parse()?.exercises;
@ -122,7 +119,7 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
let verbose = args.nocapture; let verbose = args.nocapture;
let command = args.command.unwrap_or_else(|| { let command = args.command.unwrap_or_else(|| {
println!("{DEFAULT_OUT}\n"); println!("{DEFAULT_OUT}\n");
std::process::exit(0); exit(0);
}); });
match command { match command {
@ -160,7 +157,7 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
let filter_cond = filters let filter_cond = filters
.iter() .iter()
.any(|f| exercise.name.contains(f) || fname.contains(f)); .any(|f| exercise.name.contains(f) || fname.contains(f));
let looks_done = exercise.looks_done(); let looks_done = exercise.looks_done()?;
let status = if looks_done { let status = if looks_done {
exercises_done += 1; exercises_done += 1;
"Done" "Done"
@ -185,8 +182,8 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
let mut handle = stdout.lock(); let mut handle = stdout.lock();
handle.write_all(line.as_bytes()).unwrap_or_else(|e| { handle.write_all(line.as_bytes()).unwrap_or_else(|e| {
match e.kind() { match e.kind() {
std::io::ErrorKind::BrokenPipe => std::process::exit(0), std::io::ErrorKind::BrokenPipe => exit(0),
_ => std::process::exit(1), _ => exit(1),
}; };
}); });
} }
@ -200,35 +197,34 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini
exercises.len(), exercises.len(),
percentage_progress percentage_progress
); );
std::process::exit(0); exit(0);
} }
Subcommands::Run { name } => { Subcommands::Run { name } => {
let exercise = find_exercise(&name, &exercises); let exercise = find_exercise(&name, &exercises)?;
run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1)); run(exercise, verbose).unwrap_or_else(|_| exit(1));
} }
Subcommands::Reset { name } => { Subcommands::Reset { name } => {
let exercise = find_exercise(&name, &exercises); let exercise = find_exercise(&name, &exercises)?;
reset(exercise)?; reset(exercise)?;
println!("The file {} has been reset!", exercise.path.display()); println!("The file {} has been reset!", exercise.path.display());
} }
Subcommands::Hint { name } => { Subcommands::Hint { name } => {
let exercise = find_exercise(&name, &exercises); let exercise = find_exercise(&name, &exercises)?;
println!("{}", exercise.hint); println!("{}", exercise.hint);
} }
Subcommands::Verify => { Subcommands::Verify => {
verify(&exercises, (0, exercises.len()), verbose, false) verify(&exercises, (0, exercises.len()), verbose, false).unwrap_or_else(|_| exit(1));
.unwrap_or_else(|_| std::process::exit(1));
} }
Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) { Subcommands::Watch { success_hints } => match watch(&exercises, verbose, success_hints) {
Err(e) => { Err(e) => {
println!("Error: Could not watch your progress. Error message was {e:?}."); println!("Error: Could not watch your progress. Error message was {e:?}.");
println!("Most likely you've run out of disk space or your 'inotify limit' has been reached."); println!("Most likely you've run out of disk space or your 'inotify limit' has been reached.");
std::process::exit(1); exit(1);
} }
Ok(WatchStatus::Finished) => { Ok(WatchStatus::Finished) => {
println!( println!(
@ -295,25 +291,23 @@ fn spawn_watch_shell(
}); });
} }
fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise { fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> Result<&'a Exercise> {
if name == "next" { if name == "next" {
exercises for exercise in exercises {
.iter() if !exercise.looks_done()? {
.find(|e| !e.looks_done()) return Ok(exercise);
.unwrap_or_else(|| { }
println!("🎉 Congratulations! You have done all the exercises!"); }
println!("🔚 There are no more exercises to do next!");
std::process::exit(1) println!("🎉 Congratulations! You have done all the exercises!");
}) println!("🔚 There are no more exercises to do next!");
} else { exit(0);
exercises
.iter()
.find(|e| e.name == name)
.unwrap_or_else(|| {
println!("No exercise found for '{name}'!");
std::process::exit(1)
})
} }
exercises
.iter()
.find(|e| e.name == name)
.with_context(|| format!("No exercise found for '{name}'!"))
} }
enum WatchStatus { enum WatchStatus {
@ -363,17 +357,17 @@ fn watch(
&& event_path.exists() && event_path.exists()
{ {
let filepath = event_path.as_path().canonicalize().unwrap(); let filepath = event_path.as_path().canonicalize().unwrap();
let pending_exercises = // TODO: Remove unwrap
exercises let pending_exercises = exercises
.iter() .iter()
.find(|e| filepath.ends_with(&e.path)) .find(|e| filepath.ends_with(&e.path))
.into_iter() .into_iter()
.chain(exercises.iter().filter(|e| { .chain(exercises.iter().filter(|e| {
!e.looks_done() && !filepath.ends_with(&e.path) !e.looks_done().unwrap() && !filepath.ends_with(&e.path)
})); }));
let num_done = exercises let num_done = exercises
.iter() .iter()
.filter(|e| e.looks_done() && !filepath.ends_with(&e.path)) .filter(|e| e.looks_done().unwrap() && !filepath.ends_with(&e.path))
.count(); .count();
clear_screen(); clear_screen();
match verify( match verify(

View file

@ -79,7 +79,7 @@ fn compile_only(exercise: &Exercise, success_hints: bool) -> Result<bool> {
let _ = exercise.run()?; let _ = exercise.run()?;
progress_bar.finish_and_clear(); progress_bar.finish_and_clear();
Ok(prompt_for_completion(exercise, None, success_hints)) prompt_for_completion(exercise, None, success_hints)
} }
// Compile the given Exercise and run the resulting binary in an interactive mode // Compile the given Exercise and run the resulting binary in an interactive mode
@ -102,7 +102,7 @@ fn compile_and_run_interactively(exercise: &Exercise, success_hints: bool) -> Re
bail!("TODO"); bail!("TODO");
} }
Ok(prompt_for_completion(exercise, Some(output), success_hints)) prompt_for_completion(exercise, Some(output), success_hints)
} }
// Compile the given Exercise as a test harness and display // Compile the given Exercise as a test harness and display
@ -139,7 +139,7 @@ fn compile_and_test(
} }
if run_mode == RunMode::Interactive { if run_mode == RunMode::Interactive {
Ok(prompt_for_completion(exercise, None, success_hints)) prompt_for_completion(exercise, None, success_hints)
} else { } else {
Ok(true) Ok(true)
} }
@ -149,9 +149,9 @@ fn prompt_for_completion(
exercise: &Exercise, exercise: &Exercise,
prompt_output: Option<Output>, prompt_output: Option<Output>,
success_hints: bool, success_hints: bool,
) -> bool { ) -> Result<bool> {
let context = match exercise.state() { let context = match exercise.state()? {
State::Done => return true, State::Done => return Ok(true),
State::Pending(context) => context, State::Pending(context) => context,
}; };
match exercise.mode { match exercise.mode {
@ -215,7 +215,7 @@ fn prompt_for_completion(
); );
} }
false Ok(false)
} }
fn separator() -> console::StyledObject<&'static str> { fn separator() -> console::StyledObject<&'static str> {