Skip to main content

sofie_db/
file.rs

1use sofie_core::{Error, Result};
2
3/// A file the binary carries: its source path, which development rereads, and its bytes.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub struct File {
6    pub path: &'static str,
7    pub bytes: &'static [u8],
8}
9
10impl File {
11    #[must_use]
12    pub const fn new(path: &'static str, bytes: &'static [u8]) -> Self {
13        Self { path, bytes }
14    }
15
16    #[must_use]
17    pub fn name(&self) -> &'static str {
18        self.path.rsplit('/').next().unwrap_or(self.path)
19    }
20
21    #[must_use]
22    pub fn stem(&self) -> &'static str {
23        let name = self.name();
24        name.split_once('.').map_or(name, |(stem, _)| stem)
25    }
26
27    pub fn text(&self) -> Result<&'static str> {
28        std::str::from_utf8(self.bytes)
29            .map_err(|error| Error::internal(format!("reading {} as text", self.path), error))
30    }
31}
32
33/// A file by its path from the calling crate's root, the path a development build rereads;
34/// `compiled = ` embeds what the build script wrote to `OUT_DIR` from it instead, and
35/// `embed!("folder", [names])` gives a `&[File]`.
36#[macro_export]
37macro_rules! embed {
38    ($folder:literal, [$($name:literal),+ $(,)?]) => {
39        &[$($crate::embed!(concat!($folder, "/", $name))),+]
40    };
41    ($path:expr, compiled = $compiled:expr) => {
42        $crate::File {
43            path: concat!(env!("CARGO_MANIFEST_DIR"), "/", $path),
44            bytes: include_bytes!(concat!(env!("OUT_DIR"), "/", $compiled)),
45        }
46    };
47    ($path:expr) => {
48        $crate::File {
49            path: concat!(env!("CARGO_MANIFEST_DIR"), "/", $path),
50            bytes: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path)),
51        }
52    };
53}
54
55#[cfg(test)]
56mod tests {
57    use super::File;
58
59    #[test]
60    fn a_file_is_named_by_the_last_part_of_its_path() {
61        let file = File::new("/app/modules/order/lang/de.toml", b"");
62
63        assert_eq!(file.name(), "de.toml");
64        assert_eq!(file.stem(), "de");
65        assert_eq!(File::new("0001_a.sql", b"").name(), "0001_a.sql");
66    }
67
68    #[test]
69    fn embedding_keeps_the_path_a_development_build_reads() {
70        let file = crate::embed!("Cargo.toml");
71
72        assert!(file.path.ends_with("/sofie-db/Cargo.toml"), "{}", file.path);
73        assert_eq!(
74            file.text().unwrap(),
75            std::fs::read_to_string(file.path).unwrap()
76        );
77    }
78
79    #[test]
80    fn text_that_is_not_utf8_names_its_file() {
81        let error = File::new("0001_a.sql", b"\xff").text().unwrap_err();
82
83        assert!(
84            error.to_string().starts_with("reading 0001_a.sql as text"),
85            "{error}"
86        );
87    }
88}