Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions rustlings-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,8 @@ struct InfoFile<'a> {

#[proc_macro]
pub fn include_files(_: TokenStream) -> TokenStream {
// Remove `\r` on Windows
let info_file = String::from_utf8(
include_bytes!("../info.toml")
.iter()
.copied()
.filter(|c| *c != b'\r')
Comment thread
senekor marked this conversation as resolved.
.collect(),
)
.expect("Failed to parse `info.toml` as UTF8");
let exercises = toml::de::from_str::<InfoFile>(&info_file)
let info_file = include_str!("../info.toml");
let exercises = toml::de::from_str::<InfoFile>(info_file)
.expect("Failed to parse `info.toml`")
.exercises;

Expand Down
19 changes: 9 additions & 10 deletions src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub struct AppState {
exercises: Vec<Exercise>,
// Cache the number of done exercises to avoid iterating over all exercises every time.
n_done: u32,
final_message: &'static str,
final_message: String,
state_file: File,
// Preallocated buffer for reading and writing the state file.
file_buf: Vec<u8>,
Expand All @@ -66,8 +66,8 @@ pub struct AppState {

impl AppState {
pub fn new(
exercise_infos: Vec<ExerciseInfo>,
final_message: &'static str,
exercise_infos: Vec<ExerciseInfo<'static>>,
final_message: String,
editor: Option<Editor>,
vs_code_term: bool,
) -> Result<(Self, StateFileStatus)> {
Expand Down Expand Up @@ -111,13 +111,12 @@ impl AppState {
Exercise {
name: exercise_info.name,
dir: exercise_info.dir,
// Leaking for `Editor::open`.
// Leaking is fine since the app state exists until the end of the program.
// LEAKING: For `Editor::open`. The app state is used until the end of the program.
path: exercise_info.path().leak(),
canonical_path,
test: exercise_info.test,
strict_clippy: exercise_info.strict_clippy,
hint: exercise_info.hint.trim_ascii(),
hint: exercise_info.hint,
// Updated below.
done: false,
}
Expand Down Expand Up @@ -549,9 +548,9 @@ impl AppState {
clear_terminal(stdout)?;
stdout.write_all(FINISH_LINE.as_bytes())?;

let final_message = self.final_message.trim_ascii();
let final_message = self.final_message.as_bytes().trim_ascii();
if !final_message.is_empty() {
stdout.write_all(final_message.as_bytes())?;
stdout.write_all(final_message)?;
stdout.write_all(b"\n")?;
}

Expand Down Expand Up @@ -617,7 +616,7 @@ mod tests {
canonical_path: None,
test: false,
strict_clippy: false,
hint: "",
hint: String::new(),
done: false,
}
}
Expand All @@ -628,7 +627,7 @@ mod tests {
current_exercise_ind: 0,
exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()],
n_done: 0,
final_message: "",
final_message: String::new(),
state_file: tempfile::tempfile().unwrap(),
file_buf: Vec::new(),
official_exercises: true,
Expand Down
4 changes: 2 additions & 2 deletions src/cargo_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ mod tests {
dir: None,
test: true,
strict_clippy: true,
hint: "",
hint: String::new(),
skip_check_unsolved: false,
},
ExerciseInfo {
name: "2",
dir: Some("d"),
test: false,
strict_clippy: false,
hint: "",
hint: String::new(),
skip_check_unsolved: false,
},
];
Expand Down
2 changes: 1 addition & 1 deletion src/dev/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ pub fn check(require_solutions: bool) -> Result<()> {
check_cargo_toml(&info_file.exercises, "Cargo.toml", b"")?;
}

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

Expand Down
2 changes: 1 addition & 1 deletion src/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pub struct Exercise {
pub canonical_path: Option<String>,
pub test: bool,
pub strict_clippy: bool,
pub hint: &'static str,
pub hint: String,
pub done: bool,
}

Expand Down
32 changes: 15 additions & 17 deletions src/info_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise};

/// Deserialized from the `info.toml` file.
#[derive(Deserialize)]
pub struct ExerciseInfo {
pub struct ExerciseInfo<'a> {
/// Exercise's unique name.
pub name: &'static str,
pub name: &'a str,
/// Exercise's directory name inside the `exercises/` directory.
pub dir: Option<&'static str>,
pub dir: Option<&'a str>,
/// Run `cargo test` on the exercise.
#[serde(default = "default_true")]
pub test: bool,
/// Deny all Clippy warnings.
#[serde(default)]
pub strict_clippy: bool,
/// The exercise's hint to be shown to the user on request.
pub hint: &'static str,
pub hint: String,
/// The exercise is already solved. Ignore it when checking that all exercises are unsolved.
#[serde(default)]
pub skip_check_unsolved: bool,
Expand All @@ -27,7 +27,7 @@ const fn default_true() -> bool {
true
}

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

impl RunnableExercise for ExerciseInfo {
impl RunnableExercise for ExerciseInfo<'_> {
fn name(&self) -> &str {
self.name
}
Expand All @@ -77,27 +77,25 @@ pub struct InfoFile {
/// For possible breaking changes in the future for community exercises.
pub format_version: u8,
/// Shown to users when starting with the exercises.
pub welcome_message: Option<&'static str>,
#[serde(default)]
pub welcome_message: String,
/// Shown to users after finishing all exercises.
pub final_message: Option<&'static str>,
#[serde(default)]
pub final_message: String,
/// List of all exercises.
pub exercises: Vec<ExerciseInfo>,
#[serde(borrow)]
pub exercises: Vec<ExerciseInfo<'static>>,
}

impl InfoFile {
/// Official exercises: Parse the embedded `info.toml` file.
/// Community exercises: Parse the `info.toml` file in the current directory.
pub fn parse() -> Result<Self> {
// Read a local `info.toml` if it exists.
let slf = match fs::read("info.toml") {
let slf = match fs::read_to_string("info.toml") {
Ok(file_content) => {
// Remove `\r` on Windows.
// Leaking is fine since the info file is used until the end of the program.
let file_content =
String::from_utf8(file_content.into_iter().filter(|c| *c != b'\r').collect())
.context("Failed to parse `info.toml` as UTF8")?
.leak();
toml::de::from_str::<Self>(file_content)
// LEAKING: The info file is used until the end of the program.
toml::de::from_str::<Self>(file_content.leak())
.context("Failed to parse the `info.toml` file")?
}
Err(e) => {
Expand Down
17 changes: 7 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,22 @@ fn main() -> Result<ExitCode> {

let (mut app_state, state_file_status) = AppState::new(
info_file.exercises,
info_file.final_message.unwrap_or_default(),
info_file.final_message,
editor,
vs_code_term,
)?;

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

let welcome_message = welcome_message.trim_ascii();
write!(
stdout,
"{welcome_message}\n\n\
Press ENTER to continue "
)?;
stdout.write_all(welcome_message)?;
stdout.write_all(b"\n\nPress ENTER to continue ")?;
press_enter_prompt(&mut stdout)?;
clear_terminal(&mut stdout)?;
// Flush to be able to show errors occurring before printing a newline to stdout.
Expand All @@ -106,8 +104,7 @@ fn main() -> Result<ExitCode> {
let notify_exercise_names = if args.manual_run {
None
} else {
// For the notify event handler thread.
// Leaking is fine since the slice is used until the end of the program.
// LEAKING: For the notify event handler thread. The slice is used until the end of the program.
Some(
&*app_state
.exercises()
Expand Down Expand Up @@ -176,7 +173,7 @@ fn main() -> Result<ExitCode> {
current_exercise.terminal_file_link(&mut stdout, app_state.emit_file_links())?;

stdout.write_all(b"\n\nHint:\n")?;
stdout.write_all(current_exercise.hint.as_bytes())?;
stdout.write_all(current_exercise.hint.as_bytes().trim_ascii())?;
stdout.write_all(b"\n")?;
}
// Handled in an earlier match.
Expand Down
8 changes: 7 additions & 1 deletion src/watch/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,13 @@ impl<'a> WatchState<'a> {
stdout.queue(ResetColor)?;
stdout.write_all(b"\n")?;

stdout.write_all(self.app_state.current_exercise().hint.as_bytes())?;
stdout.write_all(
self.app_state
.current_exercise()
.hint
.as_bytes()
.trim_ascii(),
)?;
stdout.write_all(b"\n\n")?;
}

Expand Down