summaryrefslogtreecommitdiff
path: root/src/embedded.rs
blob: 756b41410b25b170dfac75553853c1e8a747adaf (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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use anyhow::{bail, Context, Error, Result};
use std::{
    fs::{create_dir, create_dir_all, OpenOptions},
    io::{self, Write},
};

use crate::info_file::ExerciseInfo;

pub static EMBEDDED_FILES: EmbeddedFiles = rustlings_macros::include_files!();

#[derive(Clone, Copy)]
pub enum WriteStrategy {
    IfNotExists,
    Overwrite,
}

impl WriteStrategy {
    fn write(self, path: &str, content: &[u8]) -> Result<()> {
        let file = match self {
            Self::IfNotExists => OpenOptions::new().create_new(true).write(true).open(path),
            Self::Overwrite => OpenOptions::new()
                .create(true)
                .write(true)
                .truncate(true)
                .open(path),
        };

        file.context("Failed to open the file `{path}` in write mode")?
            .write_all(content)
            .context("Failed to write the file {path}")
    }
}

struct ExerciseFiles {
    exercise: &'static [u8],
    solution: &'static [u8],
}

struct ExerciseDir {
    name: &'static str,
    readme: &'static [u8],
}

impl ExerciseDir {
    fn init_on_disk(&self) -> Result<()> {
        let path_prefix = "exercises/";
        let readme_path_postfix = "/README.md";
        let mut dir_path =
            String::with_capacity(path_prefix.len() + self.name.len() + readme_path_postfix.len());
        dir_path.push_str(path_prefix);
        dir_path.push_str(self.name);

        if let Err(e) = create_dir(&dir_path) {
            if e.kind() == io::ErrorKind::AlreadyExists {
                return Ok(());
            }

            return Err(
                Error::from(e).context(format!("Failed to create the directory {dir_path}"))
            );
        }

        let readme_path = {
            dir_path.push_str(readme_path_postfix);
            dir_path
        };
        WriteStrategy::Overwrite.write(&readme_path, self.readme)?;

        Ok(())
    }
}

pub struct EmbeddedFiles {
    exercise_files: &'static [ExerciseFiles],
    exercise_dirs: &'static [ExerciseDir],
}

impl EmbeddedFiles {
    pub fn init_exercises_dir(&self, exercise_infos: &[ExerciseInfo]) -> Result<()> {
        create_dir("exercises").context("Failed to create the directory `exercises`")?;

        WriteStrategy::IfNotExists.write(
            "exercises/README.md",
            include_bytes!("../exercises/README.md"),
        )?;

        for dir in self.exercise_dirs {
            dir.init_on_disk()?;
        }

        for (exercise_info, exercise_files) in exercise_infos.iter().zip(self.exercise_files) {
            WriteStrategy::IfNotExists.write(&exercise_info.path(), exercise_files.exercise)?;
        }

        Ok(())
    }

    pub fn write_exercise_to_disk(
        &self,
        exercise_ind: usize,
        dir_name: &str,
        path: &str,
    ) -> Result<()> {
        let Some(dir) = self.exercise_dirs.iter().find(|dir| dir.name == dir_name) else {
            bail!("`{dir_name}` not found in the embedded directories");
        };

        dir.init_on_disk()?;
        WriteStrategy::Overwrite.write(path, self.exercise_files[exercise_ind].exercise)
    }

    pub fn write_solution_to_disk(
        &self,
        exercise_ind: usize,
        dir_name: &str,
        exercise_name: &str,
    ) -> Result<()> {
        let dir_path = format!("solutions/{dir_name}");
        create_dir_all(&dir_path).context("Failed to create the directory {dir_path}")?;

        WriteStrategy::Overwrite.write(
            &format!("{dir_path}/{exercise_name}.rs"),
            self.exercise_files[exercise_ind].solution,
        )
    }
}