summaryrefslogtreecommitdiff
path: root/rustlings-macros/src/lib.rs
blob: 598b5c357a2f1e6be51790fb26ffdf9a404b2bed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use proc_macro::TokenStream;
use quote::quote;
use std::{fs::read_dir, panic, path::PathBuf};

fn path_to_string(path: PathBuf) -> String {
    path.into_os_string()
        .into_string()
        .unwrap_or_else(|original| {
            panic!("The path {} is invalid UTF8", original.to_string_lossy());
        })
}

#[proc_macro]
pub fn include_files(_: TokenStream) -> TokenStream {
    let mut files = Vec::with_capacity(8);
    let mut dirs = Vec::with_capacity(128);

    for entry in read_dir("exercises").expect("Failed to open the exercises directory") {
        let entry = entry.expect("Failed to read the exercises directory");

        if entry.file_type().unwrap().is_file() {
            let path = entry.path();
            if path.file_name().unwrap() != "README.md" {
                files.push(path_to_string(path));
            }

            continue;
        }

        let dir_path = entry.path();
        let dir_files = read_dir(&dir_path).unwrap_or_else(|e| {
            panic!("Failed to open the directory {}: {e}", dir_path.display());
        });
        let dir_path = path_to_string(dir_path);
        let dir_files = dir_files.filter_map(|entry| {
            let entry = entry.unwrap_or_else(|e| {
                panic!("Failed to read the directory {dir_path}: {e}");
            });
            let path = entry.path();

            if !entry.file_type().unwrap().is_file() {
                panic!("Found {} but expected only files", path.display());
            }

            if path.file_name().unwrap() == "README.md" {
                return None;
            }

            if path.extension() != Some("rs".as_ref()) {
                panic!(
                    "Found {} but expected only README.md and .rs files",
                    path.display(),
                );
            }

            Some(path_to_string(path))
        });

        dirs.push(quote! {
            EmbeddedFlatDir {
                path: #dir_path,
                readme: EmbeddedFile {
                    path: ::std::concat!(#dir_path, "/README.md"),
                    content: ::std::include_bytes!(::std::concat!("../", #dir_path, "/README.md")),
                },
                content: &[
                    #(EmbeddedFile {
                        path: #dir_files,
                        content: ::std::include_bytes!(::std::concat!("../", #dir_files)),
                    }),*
                ],
            }
        });
    }

    quote! {
        EmbeddedFiles {
            info_toml_content: ::std::include_str!("../info.toml"),
            exercises_dir: ExercisesDir {
                readme: EmbeddedFile {
                    path: "exercises/README.md",
                    content: ::std::include_bytes!("../exercises/README.md"),
                },
                files: &[#(
                     EmbeddedFile {
                        path: #files,
                        content: ::std::include_bytes!(::std::concat!("../", #files)),
                    }
                ),*],
                dirs: &[#(#dirs),*],
            },
        }
    }
    .into()
}