mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-26 00:00:03 +03:00
Some improvements to error handling
This commit is contained in:
parent
82b563f165
commit
c1de4d46aa
|
@ -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]
|
||||||
|
|
84
src/main.rs
84
src/main.rs
|
@ -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(
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
Loading…
Reference in a new issue