write input files to disk before running exercise

This commit is contained in:
Remo Senekowitsch 2026-08-10 22:10:20 +02:00
parent 343e21b3bd
commit c114fb3bbb
No known key found for this signature in database
4 changed files with 44 additions and 7 deletions

View File

@ -81,9 +81,11 @@ impl AppState {
})?; })?;
let dir_canonical_path = term::canonicalize("exercises"); let dir_canonical_path = term::canonicalize("exercises");
let official_exercises = !Path::new("info.toml").exists();
let mut exercises = exercise_infos let mut exercises = exercise_infos
.into_iter() .into_iter()
.map(|exercise_info| { .enumerate()
.map(|(i, exercise_info)| {
let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| { let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| {
let mut canonical_path; let mut canonical_path;
if let Some(dir) = exercise_info.dir { if let Some(dir) = exercise_info.dir {
@ -105,10 +107,16 @@ impl AppState {
canonical_path.push_str(".rs"); canonical_path.push_str(".rs");
canonical_path canonical_path
}); });
let embedded_input_files = if official_exercises {
EMBEDDED_FILES.exercise_files[i].input_files
} else {
&[]
};
Exercise { Exercise {
name: exercise_info.name, name: exercise_info.name,
dir: exercise_info.dir, dir: exercise_info.dir,
embedded_input_files,
// LEAKING: For `Editor::open`. The app state is used 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(), path: exercise_info.path().leak(),
canonical_path, canonical_path,
@ -173,7 +181,7 @@ impl AppState {
final_message, final_message,
state_file, state_file,
file_buf, file_buf,
official_exercises: !Path::new("info.toml").exists(), official_exercises,
cmd_runner, cmd_runner,
// VS Code has its own file link handling // VS Code has its own file link handling
emit_file_links: !vs_code_term, emit_file_links: !vs_code_term,
@ -597,6 +605,7 @@ mod tests {
Exercise { Exercise {
name: "0", name: "0",
dir: None, dir: None,
embedded_input_files: &[],
path: "exercises/0.rs", path: "exercises/0.rs",
canonical_path: None, canonical_path: None,
test: false, test: false,

View File

@ -10,7 +10,7 @@ use crate::info_file::ExerciseInfo;
pub static EMBEDDED_FILES: EmbeddedFiles = rustlings_macros::include_files!(); pub static EMBEDDED_FILES: EmbeddedFiles = rustlings_macros::include_files!();
// Files related to one exercise. // Files related to one exercise.
struct ExerciseFiles { pub struct ExerciseFiles {
// The content of the exercise file. // The content of the exercise file.
exercise: &'static [u8], exercise: &'static [u8],
// The content of the solution file. // The content of the solution file.
@ -18,7 +18,7 @@ struct ExerciseFiles {
// Index of the related `ExerciseDir` in `EmbeddedFiles::exercise_dirs`. // Index of the related `ExerciseDir` in `EmbeddedFiles::exercise_dirs`.
dir_ind: usize, dir_ind: usize,
// Files that are read by the exercise. // Files that are read by the exercise.
input_files: &'static [InputFile], pub input_files: &'static [InputFile],
} }
// Input files that may be read by exercises. // Input files that may be read by exercises.
@ -64,7 +64,7 @@ impl ExerciseDir {
pub struct EmbeddedFiles { pub struct EmbeddedFiles {
/// The content of the `info.toml` file. /// The content of the `info.toml` file.
pub info_file: &'static str, pub info_file: &'static str,
exercise_files: &'static [ExerciseFiles], pub exercise_files: &'static [ExerciseFiles],
pub exercise_dirs: &'static [ExerciseDir], pub exercise_dirs: &'static [ExerciseDir],
} }

View File

@ -1,4 +1,4 @@
use anyhow::Result; use anyhow::{Context, Result};
use crossterm::{ use crossterm::{
QueueableCommand, QueueableCommand,
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor}, style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
@ -7,6 +7,7 @@ use std::io::{self, StdoutLock, Write};
use crate::{ use crate::{
cmd::CmdRunner, cmd::CmdRunner,
embedded::InputFile,
term::{self, CountedWrite, file_path, terminal_file_link, write_ansi}, term::{self, CountedWrite, file_path, terminal_file_link, write_ansi},
}; };
@ -69,6 +70,7 @@ fn run_bin(
pub struct Exercise { pub struct Exercise {
pub name: &'static str, pub name: &'static str,
pub dir: Option<&'static str>, pub dir: Option<&'static str>,
pub embedded_input_files: &'static [InputFile],
/// Path of the exercise file starting with the `exercises/` directory. /// Path of the exercise file starting with the `exercises/` directory.
pub path: &'static str, pub path: &'static str,
pub canonical_path: Option<String>, pub canonical_path: Option<String>,
@ -99,6 +101,7 @@ pub trait RunnableExercise {
fn dir(&self) -> Option<&str>; fn dir(&self) -> Option<&str>;
fn strict_clippy(&self) -> bool; fn strict_clippy(&self) -> bool;
fn test(&self) -> bool; fn test(&self) -> bool;
fn embedded_input_files(&self) -> &[InputFile];
// Compile, check and run the exercise or its solution (depending on `bin_name´). // Compile, check and run the exercise or its solution (depending on `bin_name´).
// The output is written to the `output` buffer after clearing it. // The output is written to the `output` buffer after clearing it.
@ -112,6 +115,19 @@ pub trait RunnableExercise {
output.clear(); output.clear();
} }
// Input files are already written to disk during `rustlings init`.
// We repeat it here to ensure an exercise doesn't fail because the
// input files were deleted or modified in the meantime. Note that
// `self.embedded_input_files()` is empty for community exercises.
for input_file in self.embedded_input_files() {
let name = input_file.name;
let path = match self.dir() {
Some(dir) => format!("exercises/{dir}/{name}"),
None => format!("exercises/{name}"),
};
std::fs::write(path, input_file.content).context("failed to write input file")?;
}
let build_success = cmd_runner let build_success = cmd_runner
.cargo("build", bin_name, output.as_deref_mut()) .cargo("build", bin_name, output.as_deref_mut())
.run("cargo build …")?; .run("cargo build …")?;
@ -224,4 +240,8 @@ impl RunnableExercise for Exercise {
fn test(&self) -> bool { fn test(&self) -> bool {
self.test self.test
} }
fn embedded_input_files(&self) -> &[InputFile] {
self.embedded_input_files
}
} }

View File

@ -2,7 +2,10 @@ use anyhow::{Context, Error, Result, bail};
use serde::Deserialize; use serde::Deserialize;
use std::{fs, io::ErrorKind}; use std::{fs, io::ErrorKind};
use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise}; use crate::{
embedded::{EMBEDDED_FILES, InputFile},
exercise::RunnableExercise,
};
/// Deserialized from the `info.toml` file. /// Deserialized from the `info.toml` file.
#[derive(Deserialize)] #[derive(Deserialize)]
@ -72,6 +75,11 @@ impl RunnableExercise for ExerciseInfo<'_> {
fn test(&self) -> bool { fn test(&self) -> bool {
self.test self.test
} }
fn embedded_input_files(&self) -> &[InputFile] {
// We don't have the input files embedded for communtiy exercises.
&[]
}
} }
/// The deserialized `info.toml` file. /// The deserialized `info.toml` file.