1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
use anyhow::{bail, Context, Result};
use clap::{Parser, Subcommand};
use std::{path::Path, process::exit};
mod consts;
mod embedded;
mod exercise;
mod init;
mod list;
mod progress_bar;
mod run;
mod state_file;
mod verify;
mod watch;
use self::{
consts::WELCOME,
exercise::{Exercise, InfoFile},
list::list,
run::run,
state_file::StateFile,
verify::{verify, VerifyState},
watch::{watch, WatchExit},
};
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
#[derive(Parser)]
#[command(version)]
struct Args {
#[command(subcommand)]
command: Option<Subcommands>,
}
#[derive(Subcommand)]
enum Subcommands {
/// Initialize Rustlings
Init,
/// Verify all exercises according to the recommended order
Verify,
/// Same as just running `rustlings` without a subcommand.
Watch,
/// Run/Test a single exercise
Run {
/// The name of the exercise
name: String,
},
/// Reset a single exercise
Reset {
/// The name of the exercise
name: String,
},
/// Return a hint for the given exercise
Hint {
/// The name of the exercise
name: String,
},
}
fn find_exercise(name: &str, exercises: &'static [Exercise]) -> Result<(usize, &'static Exercise)> {
if name == "next" {
for (ind, exercise) in exercises.iter().enumerate() {
if !exercise.looks_done()? {
return Ok((ind, exercise));
}
}
println!("🎉 Congratulations! You have done all the exercises!");
println!("🔚 There are no more exercises to do next!");
exit(0);
}
exercises
.iter()
.enumerate()
.find(|(_, exercise)| exercise.name == name)
.with_context(|| format!("No exercise found for '{name}'!"))
}
fn main() -> Result<()> {
let args = Args::parse();
which::which("cargo").context(
"Failed to find `cargo`.
Did you already install Rust?
Try running `cargo --version` to diagnose the problem.",
)?;
let mut info_file = InfoFile::parse()?;
info_file.exercises.shrink_to_fit();
// Leaking is not a problem since the exercises' slice is used until the end of the program.
let exercises = info_file.exercises.leak();
if matches!(args.command, Some(Subcommands::Init)) {
init::init(exercises).context("Initialization failed")?;
println!(
"\nDone initialization!\n
Run `cd rustlings` to go into the generated directory.
Then run `rustlings` for further instructions on getting started."
);
return Ok(());
} else if !Path::new("exercises").is_dir() {
println!(
"
{WELCOME}
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."
);
exit(1);
}
let mut state_file = StateFile::read_or_default(exercises);
match args.command {
None | Some(Subcommands::Watch) => loop {
match watch(&mut state_file, exercises)? {
WatchExit::Shutdown => break,
// It is much easier to exit the watch mode, launch the list mode and then restart
// the watch mode instead of trying to pause the watch threads and correct the
// watch state.
WatchExit::List => list(&mut state_file, exercises)?,
}
},
// `Init` is handled above.
Some(Subcommands::Init) => (),
Some(Subcommands::Run { name }) => {
let (_, exercise) = find_exercise(&name, exercises)?;
run(exercise).unwrap_or_else(|_| exit(1));
}
Some(Subcommands::Reset { name }) => {
let (ind, exercise) = find_exercise(&name, exercises)?;
exercise.reset()?;
state_file.reset(ind)?;
println!("The exercise {exercise} has been reset!");
}
Some(Subcommands::Hint { name }) => {
let (_, exercise) = find_exercise(&name, exercises)?;
println!("{}", exercise.hint);
}
Some(Subcommands::Verify) => match verify(exercises, 0)? {
VerifyState::AllExercisesDone => println!("All exercises done!"),
VerifyState::Failed(exercise) => bail!("Exercise {exercise} failed"),
},
}
Ok(())
}
|