rustlings_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use serde::Deserialize;
4
5#[derive(Deserialize)]
6struct ExerciseInfo {
7    name: String,
8    dir: String,
9}
10
11#[derive(Deserialize)]
12struct InfoFile {
13    exercises: Vec<ExerciseInfo>,
14}
15
16#[proc_macro]
17pub fn include_files(_: TokenStream) -> TokenStream {
18    let info_file = include_str!("../info.toml");
19    let exercises = toml::de::from_str::<InfoFile>(info_file)
20        .expect("Failed to parse `info.toml`")
21        .exercises;
22
23    let exercise_files = exercises
24        .iter()
25        .map(|exercise| format!("../exercises/{}/{}.rs", exercise.dir, exercise.name));
26    let solution_files = exercises
27        .iter()
28        .map(|exercise| format!("../solutions/{}/{}.rs", exercise.dir, exercise.name));
29
30    let mut dirs = Vec::with_capacity(32);
31    let mut dir_inds = vec![0; exercises.len()];
32
33    for (exercise, dir_ind) in exercises.iter().zip(&mut dir_inds) {
34        // The directory is often the last one inserted.
35        if let Some(ind) = dirs.iter().rev().position(|dir| *dir == exercise.dir) {
36            *dir_ind = dirs.len() - 1 - ind;
37            continue;
38        }
39
40        dirs.push(exercise.dir.as_str());
41        *dir_ind = dirs.len() - 1;
42    }
43
44    let readmes = dirs
45        .iter()
46        .map(|dir| format!("../exercises/{dir}/README.md"));
47
48    quote! {
49        EmbeddedFiles {
50            info_file: #info_file,
51            exercise_files: &[#(ExerciseFiles { exercise: include_bytes!(#exercise_files), solution: include_bytes!(#solution_files), dir_ind: #dir_inds }),*],
52            exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*]
53        }
54    }
55    .into()
56}