From f63e11aeb6d87bb13a8419726206ddbb7b129fce Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sun, 9 Aug 2026 14:06:43 +0200 Subject: [PATCH] move input files into exercise directory --- exercises/24_async/async1.rs | 10 ++++-- .../24_async}/scores_class_a.txt | 0 .../24_async}/scores_class_b.txt | 0 .../24_async}/scores_class_c.txt | 0 rustlings-macros/info.toml | 14 +++----- rustlings-macros/src/lib.rs | 36 +++++++++++-------- solutions/24_async/async1.rs | 10 ++++-- src/embedded.rs | 9 ++++- src/init.rs | 9 ----- 9 files changed, 49 insertions(+), 39 deletions(-) rename {input_files => exercises/24_async}/scores_class_a.txt (100%) rename {input_files => exercises/24_async}/scores_class_b.txt (100%) rename {input_files => exercises/24_async}/scores_class_c.txt (100%) diff --git a/exercises/24_async/async1.rs b/exercises/24_async/async1.rs index c57efbed..aa61ea70 100644 --- a/exercises/24_async/async1.rs +++ b/exercises/24_async/async1.rs @@ -6,14 +6,18 @@ // Let's simulate this using asynchronous programming. Each person is // represented as an asynchronous task, which can be executed concurrently. +const SCORES_CLASS_A: &str = "exercises/24_async/scores_class_a.txt"; +const SCORES_CLASS_B: &str = "exercises/24_async/scores_class_b.txt"; +const SCORES_CLASS_C: &str = "exercises/24_async/scores_class_c.txt"; + // Async tasks need to be executed by a "runtime", which is not provided by // Rust's standard library. Here, we use the mainstream runtime `tokio`. // The macro `tokio::main` wraps the entire main function in a runtime. #[tokio::main] async fn main() { - let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt")); - let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt")); - let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt")); + let mean_score_a = tokio::spawn(calculate_mean_score(SCORES_CLASS_A)); + let mean_score_b = tokio::spawn(calculate_mean_score(SCORES_CLASS_B)); + let mean_score_c = tokio::spawn(calculate_mean_score(SCORES_CLASS_C)); // TODO: Await the spawned tasks to check their results. assert_eq!(mean_score_a, 84); // alice diff --git a/input_files/scores_class_a.txt b/exercises/24_async/scores_class_a.txt similarity index 100% rename from input_files/scores_class_a.txt rename to exercises/24_async/scores_class_a.txt diff --git a/input_files/scores_class_b.txt b/exercises/24_async/scores_class_b.txt similarity index 100% rename from input_files/scores_class_b.txt rename to exercises/24_async/scores_class_b.txt diff --git a/input_files/scores_class_c.txt b/exercises/24_async/scores_class_c.txt similarity index 100% rename from input_files/scores_class_c.txt rename to exercises/24_async/scores_class_c.txt diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index d31fb4c7..01c47c4f 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -1218,6 +1218,11 @@ Add `AsRef` or `AsMut` as a trait bound to the functions.""" name = "async1" dir = "24_async" test = false +input_files = [ + "scores_class_a.txt", + "scores_class_b.txt", + "scores_class_c.txt", +] hint = """ Asynchronous runtimes like tokio can only spawn tasks that are defined as async functions, not regular ones. Add the "async" keyword before the "fn" keyword of @@ -1225,12 +1230,3 @@ the functions "tim", "carl" and "nick". An async task can wait for another one to complete by "awaiting" it. Add ".await" after the three "task_name" variables in the "block_on" call.""" - -[[input_files]] -name = "scores_class_a.txt" - -[[input_files]] -name = "scores_class_b.txt" - -[[input_files]] -name = "scores_class_c.txt" diff --git a/rustlings-macros/src/lib.rs b/rustlings-macros/src/lib.rs index a4104851..b672b850 100644 --- a/rustlings-macros/src/lib.rs +++ b/rustlings-macros/src/lib.rs @@ -6,18 +6,14 @@ use serde::Deserialize; struct ExerciseInfo<'a> { name: &'a str, dir: &'a str, -} - -#[derive(Deserialize)] -struct InputFileInfo<'a> { - name: &'a str, + #[serde(default)] + input_files: Vec<&'a str>, } #[derive(Deserialize)] struct InfoFile<'a> { #[serde(borrow)] exercises: Vec>, - input_files: Vec>, } #[proc_macro] @@ -47,22 +43,34 @@ pub fn include_files(_: TokenStream) -> TokenStream { *dir_ind = dirs.len() - 1; } + let input_files = exercises.iter().map(|exercise| { + let names = exercise.input_files.iter(); + let paths = exercise + .input_files + .iter() + .map(|f| format!("../exercises/{}/{}", exercise.dir, f)); + quote! { + &[#(InputFile { + name: #names, + content: include_str!(#paths), + }),*] + } + }); + let readmes = dirs .iter() .map(|dir| format!("../exercises/{dir}/README.md")); - let input_file_names = info.input_files.iter().map(|f| f.name); - let input_file_paths = info - .input_files - .iter() - .map(|f| format!("../input_files/{}", f.name)); - quote! { EmbeddedFiles { info_file: #info_file, - exercise_files: &[#(ExerciseFiles { exercise: include_bytes!(#exercise_files), solution: include_bytes!(#solution_files), dir_ind: #dir_inds }),*], + exercise_files: &[#(ExerciseFiles { + exercise: include_bytes!(#exercise_files), + solution: include_bytes!(#solution_files), + dir_ind: #dir_inds, + input_files: #input_files, + }),*], exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*], - input_files: &[#(InputFile { name: #input_file_names, content: include_str!(#input_file_paths) }),*], } } .into() diff --git a/solutions/24_async/async1.rs b/solutions/24_async/async1.rs index 9321447a..97121c77 100644 --- a/solutions/24_async/async1.rs +++ b/solutions/24_async/async1.rs @@ -6,14 +6,18 @@ // Let's simulate this using asynchronous programming. Each person is // represented as an asynchronous task, which can be executed concurrently. +const SCORES_CLASS_A: &str = "exercises/24_async/scores_class_a.txt"; +const SCORES_CLASS_B: &str = "exercises/24_async/scores_class_b.txt"; +const SCORES_CLASS_C: &str = "exercises/24_async/scores_class_c.txt"; + // Async tasks need to be executed by a "runtime", which is not provided by // Rust's standard library. Here, we use the mainstream runtime `tokio`. // The macro `tokio::main` wraps the entire main function in a runtime. #[tokio::main] async fn main() { - let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt")); - let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt")); - let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt")); + let mean_score_a = tokio::spawn(calculate_mean_score(SCORES_CLASS_A)); + let mean_score_b = tokio::spawn(calculate_mean_score(SCORES_CLASS_B)); + let mean_score_c = tokio::spawn(calculate_mean_score(SCORES_CLASS_C)); assert_eq!(mean_score_a.await.unwrap(), 84); // alice assert_eq!(mean_score_b.await.unwrap(), 89); // bob diff --git a/src/embedded.rs b/src/embedded.rs index 3e0c0087..36263a9c 100644 --- a/src/embedded.rs +++ b/src/embedded.rs @@ -17,6 +17,8 @@ struct ExerciseFiles { solution: &'static [u8], // Index of the related `ExerciseDir` in `EmbeddedFiles::exercise_dirs`. dir_ind: usize, + // Files that are read by the exercise. + input_files: &'static [InputFile], } // Input files that may be read by exercises. @@ -64,7 +66,6 @@ pub struct EmbeddedFiles { pub info_file: &'static str, exercise_files: &'static [ExerciseFiles], pub exercise_dirs: &'static [ExerciseDir], - pub input_files: &'static [InputFile], } impl EmbeddedFiles { @@ -97,6 +98,12 @@ impl EmbeddedFiles { fs::write(&exercise_path, exercise_files.exercise) .with_context(|| format!("Failed to write the exercise file {exercise_path}"))?; + + for InputFile { name, content } in exercise_files.input_files { + let path = format!("{prefix}/{dir_name}/{name}", dir_name = dir.name); + fs::write(&path, content) + .with_context(|| format!("Failed to write the input file {path}"))?; + } } Ok(()) diff --git a/src/init.rs b/src/init.rs index fe4332fb..3ccef756 100644 --- a/src/init.rs +++ b/src/init.rs @@ -144,15 +144,6 @@ pub fn init() -> Result<()> { .with_context(|| format!("Failed to create the file {solution_path}"))?; } - // init input files - create_dir("input_files").context("Failed to create the directory `input_files`")?; - for input_file in EMBEDDED_FILES.input_files { - fs::write( - format!("input_files/{}", input_file.name), - input_file.content, - )?; - } - let current_cargo_toml = include_str!("../dev-Cargo.toml"); // Skip the first line (comment). let newline_ind = current_cargo_toml