Skip to content

Commit a93a10a

Browse files
committed
Allow processing for hints and messages in the info file
1 parent 8560bac commit a93a10a

8 files changed

Lines changed: 44 additions & 52 deletions

File tree

rustlings-macros/src/lib.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,8 @@ struct InfoFile<'a> {
1616

1717
#[proc_macro]
1818
pub fn include_files(_: TokenStream) -> TokenStream {
19-
// Remove `\r` on Windows
20-
let info_file = String::from_utf8(
21-
include_bytes!("../info.toml")
22-
.iter()
23-
.copied()
24-
.filter(|c| *c != b'\r')
25-
.collect(),
26-
)
27-
.expect("Failed to parse `info.toml` as UTF8");
28-
let exercises = toml::de::from_str::<InfoFile>(&info_file)
19+
let info_file = include_str!("../info.toml");
20+
let exercises = toml::de::from_str::<InfoFile>(info_file)
2921
.expect("Failed to parse `info.toml`")
3022
.exercises;
3123

src/app_state.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub struct AppState {
5454
exercises: Vec<Exercise>,
5555
// Cache the number of done exercises to avoid iterating over all exercises every time.
5656
n_done: u32,
57-
final_message: &'static str,
57+
final_message: String,
5858
state_file: File,
5959
// Preallocated buffer for reading and writing the state file.
6060
file_buf: Vec<u8>,
@@ -66,8 +66,8 @@ pub struct AppState {
6666

6767
impl AppState {
6868
pub fn new(
69-
exercise_infos: Vec<ExerciseInfo>,
70-
final_message: &'static str,
69+
exercise_infos: Vec<ExerciseInfo<'static>>,
70+
final_message: String,
7171
editor: Option<Editor>,
7272
vs_code_term: bool,
7373
) -> Result<(Self, StateFileStatus)> {
@@ -111,13 +111,12 @@ impl AppState {
111111
Exercise {
112112
name: exercise_info.name,
113113
dir: exercise_info.dir,
114-
// Leaking for `Editor::open`.
115-
// Leaking is fine since the app state exists until the end of the program.
114+
// LEAKING: For `Editor::open`. The app state is used until the end of the program.
116115
path: exercise_info.path().leak(),
117116
canonical_path,
118117
test: exercise_info.test,
119118
strict_clippy: exercise_info.strict_clippy,
120-
hint: exercise_info.hint.trim_ascii(),
119+
hint: exercise_info.hint,
121120
// Updated below.
122121
done: false,
123122
}
@@ -549,9 +548,9 @@ impl AppState {
549548
clear_terminal(stdout)?;
550549
stdout.write_all(FINISH_LINE.as_bytes())?;
551550

552-
let final_message = self.final_message.trim_ascii();
551+
let final_message = self.final_message.as_bytes().trim_ascii();
553552
if !final_message.is_empty() {
554-
stdout.write_all(final_message.as_bytes())?;
553+
stdout.write_all(final_message)?;
555554
stdout.write_all(b"\n")?;
556555
}
557556

@@ -617,7 +616,7 @@ mod tests {
617616
canonical_path: None,
618617
test: false,
619618
strict_clippy: false,
620-
hint: "",
619+
hint: String::new(),
621620
done: false,
622621
}
623622
}
@@ -628,7 +627,7 @@ mod tests {
628627
current_exercise_ind: 0,
629628
exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()],
630629
n_done: 0,
631-
final_message: "",
630+
final_message: String::new(),
632631
state_file: tempfile::tempfile().unwrap(),
633632
file_buf: Vec::new(),
634633
official_exercises: true,

src/cargo_toml.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,15 @@ mod tests {
110110
dir: None,
111111
test: true,
112112
strict_clippy: true,
113-
hint: "",
113+
hint: String::new(),
114114
skip_check_unsolved: false,
115115
},
116116
ExerciseInfo {
117117
name: "2",
118118
dir: Some("d"),
119119
test: false,
120120
strict_clippy: false,
121-
hint: "",
121+
hint: String::new(),
122122
skip_check_unsolved: false,
123123
},
124124
];

src/dev/check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ pub fn check(require_solutions: bool) -> Result<()> {
383383
check_cargo_toml(&info_file.exercises, "Cargo.toml", b"")?;
384384
}
385385

386-
// Leaking is fine since they are used until the end of the program.
386+
// LEAKING: Used until the end of the program.
387387
let cmd_runner = Box::leak(Box::new(CmdRunner::build()?));
388388
let info_file = Box::leak(Box::new(info_file));
389389

src/exercise.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub struct Exercise {
7373
pub canonical_path: Option<String>,
7474
pub test: bool,
7575
pub strict_clippy: bool,
76-
pub hint: &'static str,
76+
pub hint: String,
7777
pub done: bool,
7878
}
7979

src/info_file.rs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise};
66

77
/// Deserialized from the `info.toml` file.
88
#[derive(Deserialize)]
9-
pub struct ExerciseInfo {
9+
pub struct ExerciseInfo<'a> {
1010
/// Exercise's unique name.
11-
pub name: &'static str,
11+
pub name: &'a str,
1212
/// Exercise's directory name inside the `exercises/` directory.
13-
pub dir: Option<&'static str>,
13+
pub dir: Option<&'a str>,
1414
/// Run `cargo test` on the exercise.
1515
#[serde(default = "default_true")]
1616
pub test: bool,
1717
/// Deny all Clippy warnings.
1818
#[serde(default)]
1919
pub strict_clippy: bool,
2020
/// The exercise's hint to be shown to the user on request.
21-
pub hint: &'static str,
21+
pub hint: String,
2222
/// The exercise is already solved. Ignore it when checking that all exercises are unsolved.
2323
#[serde(default)]
2424
pub skip_check_unsolved: bool,
@@ -27,7 +27,7 @@ const fn default_true() -> bool {
2727
true
2828
}
2929

30-
impl ExerciseInfo {
30+
impl ExerciseInfo<'_> {
3131
/// Path to the exercise file starting with the `exercises/` directory.
3232
pub fn path(&self) -> String {
3333
let mut path = if let Some(dir) = self.dir {
@@ -53,7 +53,7 @@ impl ExerciseInfo {
5353
}
5454
}
5555

56-
impl RunnableExercise for ExerciseInfo {
56+
impl RunnableExercise for ExerciseInfo<'_> {
5757
fn name(&self) -> &str {
5858
self.name
5959
}
@@ -77,27 +77,25 @@ pub struct InfoFile {
7777
/// For possible breaking changes in the future for community exercises.
7878
pub format_version: u8,
7979
/// Shown to users when starting with the exercises.
80-
pub welcome_message: Option<&'static str>,
80+
#[serde(default)]
81+
pub welcome_message: String,
8182
/// Shown to users after finishing all exercises.
82-
pub final_message: Option<&'static str>,
83+
#[serde(default)]
84+
pub final_message: String,
8385
/// List of all exercises.
84-
pub exercises: Vec<ExerciseInfo>,
86+
#[serde(borrow)]
87+
pub exercises: Vec<ExerciseInfo<'static>>,
8588
}
8689

8790
impl InfoFile {
8891
/// Official exercises: Parse the embedded `info.toml` file.
8992
/// Community exercises: Parse the `info.toml` file in the current directory.
9093
pub fn parse() -> Result<Self> {
9194
// Read a local `info.toml` if it exists.
92-
let slf = match fs::read("info.toml") {
95+
let slf = match fs::read_to_string("info.toml") {
9396
Ok(file_content) => {
94-
// Remove `\r` on Windows.
95-
// Leaking is fine since the info file is used until the end of the program.
96-
let file_content =
97-
String::from_utf8(file_content.into_iter().filter(|c| *c != b'\r').collect())
98-
.context("Failed to parse `info.toml` as UTF8")?
99-
.leak();
100-
toml::de::from_str::<Self>(file_content)
97+
// LEAKING: The info file is used until the end of the program.
98+
toml::de::from_str::<Self>(file_content.leak())
10199
.context("Failed to parse the `info.toml` file")?
102100
}
103101
Err(e) => {

src/main.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,22 @@ fn main() -> Result<ExitCode> {
7070

7171
let (mut app_state, state_file_status) = AppState::new(
7272
info_file.exercises,
73-
info_file.final_message.unwrap_or_default(),
73+
info_file.final_message,
7474
editor,
7575
vs_code_term,
7676
)?;
7777

7878
// Show the welcome message if the state file doesn't exist yet.
79-
if let Some(welcome_message) = info_file.welcome_message {
79+
let welcome_message = info_file.welcome_message.as_bytes().trim_ascii();
80+
if !welcome_message.is_empty() {
8081
match state_file_status {
8182
StateFileStatus::NotRead => {
8283
let mut stdout = io::stdout().lock();
8384
clear_terminal(&mut stdout)?;
8485

8586
let welcome_message = welcome_message.trim_ascii();
86-
write!(
87-
stdout,
88-
"{welcome_message}\n\n\
89-
Press ENTER to continue "
90-
)?;
87+
stdout.write_all(welcome_message)?;
88+
stdout.write_all(b"\n\nPress ENTER to continue ")?;
9189
press_enter_prompt(&mut stdout)?;
9290
clear_terminal(&mut stdout)?;
9391
// Flush to be able to show errors occurring before printing a newline to stdout.
@@ -106,8 +104,7 @@ fn main() -> Result<ExitCode> {
106104
let notify_exercise_names = if args.manual_run {
107105
None
108106
} else {
109-
// For the notify event handler thread.
110-
// Leaking is fine since the slice is used until the end of the program.
107+
// LEAKING: For the notify event handler thread. The slice is used until the end of the program.
111108
Some(
112109
&*app_state
113110
.exercises()
@@ -176,7 +173,7 @@ fn main() -> Result<ExitCode> {
176173
current_exercise.terminal_file_link(&mut stdout, app_state.emit_file_links())?;
177174

178175
stdout.write_all(b"\n\nHint:\n")?;
179-
stdout.write_all(current_exercise.hint.as_bytes())?;
176+
stdout.write_all(current_exercise.hint.as_bytes().trim_ascii())?;
180177
stdout.write_all(b"\n")?;
181178
}
182179
// Handled in an earlier match.

src/watch/state.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,13 @@ impl<'a> WatchState<'a> {
224224
stdout.queue(ResetColor)?;
225225
stdout.write_all(b"\n")?;
226226

227-
stdout.write_all(self.app_state.current_exercise().hint.as_bytes())?;
227+
stdout.write_all(
228+
self.app_state
229+
.current_exercise()
230+
.hint
231+
.as_bytes()
232+
.trim_ascii(),
233+
)?;
228234
stdout.write_all(b"\n\n")?;
229235
}
230236

0 commit comments

Comments
 (0)