static_files/mods/
resource.rs

1/*!
2Resource definition and single module based generation.
3 */
4use path_slash::PathExt;
5use std::{
6    fs::{self, File, Metadata},
7    io::{self, Write},
8    path::{Path, PathBuf},
9    time::SystemTime,
10};
11
12/// Static files resource.
13pub struct Resource {
14    pub data: &'static [u8],
15    pub modified: u64,
16    pub mime_type: &'static str,
17}
18
19/// Used internally in generated functions.
20#[inline]
21pub fn new_resource(data: &'static [u8], modified: u64, mime_type: &'static str) -> Resource {
22    Resource {
23        data,
24        modified,
25        mime_type,
26    }
27}
28
29pub(crate) const DEFAULT_VARIABLE_NAME: &str = "r";
30
31/// Generate resources for `project_dir` using `filter`.
32/// Result saved in `generated_filename` and function named as `fn_name`.
33///
34/// in `build.rs`:
35/// ```rust
36/// use std::{env, path::Path};
37/// use static_files::resource::generate_resources;
38///
39/// fn main() {
40///     let out_dir = env::var("OUT_DIR").unwrap();
41///     let generated_filename = Path::new(&out_dir).join("generated.rs");
42///     generate_resources("./tests", None, generated_filename, "generate").unwrap();
43/// }
44/// ```
45///
46/// in `main.rs`:
47/// ```rust
48/// include!(concat!(env!("OUT_DIR"), "/generated.rs"));
49///
50/// fn main() {
51///     let generated_file = generate();
52///
53///     assert_eq!(generated_file.len(), 4);
54/// }
55/// ```
56pub fn generate_resources<P: AsRef<Path>, G: AsRef<Path>>(
57    project_dir: P,
58    filter: Option<fn(p: &Path) -> bool>,
59    generated_filename: G,
60    fn_name: &str,
61) -> io::Result<()> {
62    let resources = collect_resources(&project_dir, filter)?;
63
64    let mut f = File::create(&generated_filename)?;
65
66    generate_function_header(&mut f, fn_name)?;
67    generate_uses(&mut f)?;
68
69    generate_variable_header(&mut f, DEFAULT_VARIABLE_NAME)?;
70    generate_resource_inserts(&mut f, &project_dir, DEFAULT_VARIABLE_NAME, resources)?;
71    generate_variable_return(&mut f, DEFAULT_VARIABLE_NAME)?;
72
73    generate_function_end(&mut f)?;
74
75    Ok(())
76}
77
78/// Generate resource mapping for `project_dir` using `filter`.
79/// Result saved in `generated_filename` as anonymous block which returns HashMap<&'static str, Resource>.
80///
81/// in `build.rs`:
82/// ```rust
83///
84/// use std::{env, path::Path};
85/// use static_files::resource::generate_resources_mapping;
86///
87/// fn main() {
88///     let out_dir = env::var("OUT_DIR").unwrap();
89///     let generated_filename = Path::new(&out_dir).join("generated_mapping.rs");
90///     generate_resources_mapping("./tests", None, generated_filename).unwrap();
91/// }
92/// ```
93///
94/// in `main.rs`:
95/// ```rust
96/// use std::collections::HashMap;
97///
98/// use static_files::Resource;
99///
100/// fn generate_mapping() -> HashMap<&'static str, Resource> {
101///   include!(concat!(env!("OUT_DIR"), "/generated_mapping.rs"))
102/// }
103///
104/// fn main() {
105///     let generated_file = generate_mapping();
106///
107///     assert_eq!(generated_file.len(), 4);
108///
109/// }
110/// ```
111pub fn generate_resources_mapping<P: AsRef<Path>, G: AsRef<Path>>(
112    project_dir: P,
113    filter: Option<fn(p: &Path) -> bool>,
114    generated_filename: G,
115) -> io::Result<()> {
116    let resources = collect_resources(&project_dir, filter)?;
117
118    let mut f = File::create(&generated_filename)?;
119    writeln!(f, "{{")?;
120
121    generate_uses(&mut f)?;
122
123    generate_variable_header(&mut f, DEFAULT_VARIABLE_NAME)?;
124
125    generate_resource_inserts(&mut f, &project_dir, DEFAULT_VARIABLE_NAME, resources)?;
126
127    generate_variable_return(&mut f, DEFAULT_VARIABLE_NAME)?;
128
129    writeln!(f, "}}")?;
130    Ok(())
131}
132
133#[cfg(not(feature = "sort"))]
134pub(crate) fn collect_resources<P: AsRef<Path>>(
135    path: P,
136    filter: Option<fn(p: &Path) -> bool>,
137) -> io::Result<Vec<(PathBuf, Metadata)>> {
138    collect_resources_nested(path, filter)
139}
140
141#[cfg(feature = "sort")]
142pub(crate) fn collect_resources<P: AsRef<Path>>(
143    path: P,
144    filter: Option<fn(p: &Path) -> bool>,
145) -> io::Result<Vec<(PathBuf, Metadata)>> {
146    let mut resources = collect_resources_nested(path, filter)?;
147    resources.sort_by(|a, b| a.0.cmp(&b.0));
148    Ok(resources)
149}
150
151#[inline]
152fn collect_resources_nested<P: AsRef<Path>>(
153    path: P,
154    filter: Option<fn(p: &Path) -> bool>,
155) -> io::Result<Vec<(PathBuf, Metadata)>> {
156    let mut result = vec![];
157
158    for entry in fs::read_dir(&path)? {
159        let entry = entry?;
160        let path = entry.path();
161
162        if let Some(ref filter) = filter {
163            if !filter(path.as_ref()) {
164                continue;
165            }
166        }
167
168        if path.is_dir() {
169            let nested = collect_resources(path, filter)?;
170            result.extend(nested);
171        } else {
172            result.push((path, entry.metadata()?));
173        }
174    }
175
176    Ok(result)
177}
178
179pub(crate) fn generate_resource_inserts<P: AsRef<Path>, W: Write>(
180    f: &mut W,
181    project_dir: &P,
182    variable_name: &str,
183    resources: Vec<(PathBuf, Metadata)>,
184) -> io::Result<()> {
185    for resource in &resources {
186        generate_resource_insert(f, project_dir, variable_name, resource)?;
187    }
188    Ok(())
189}
190
191#[allow(clippy::unnecessary_debug_formatting)]
192pub(crate) fn generate_resource_insert<P: AsRef<Path>, W: Write>(
193    f: &mut W,
194    project_dir: &P,
195    variable_name: &str,
196    resource: &(PathBuf, Metadata),
197) -> io::Result<()> {
198    let (path, metadata) = resource;
199    let abs_path = path.canonicalize()?;
200    let key_path = path.strip_prefix(&project_dir).unwrap().to_slash().unwrap();
201
202    let modified = if let Ok(Ok(modified)) = metadata
203        .modified()
204        .map(|x| x.duration_since(SystemTime::UNIX_EPOCH))
205    {
206        modified.as_secs()
207    } else {
208        0
209    };
210    let mime_type = mime_guess::MimeGuess::from_path(&path).first_or_octet_stream();
211    writeln!(
212        f,
213        "{}.insert({:?},n(i!({:?}),{:?},{:?}));",
214        variable_name, &key_path, &abs_path, modified, &mime_type,
215    )
216}
217
218pub(crate) fn generate_function_header<F: Write>(f: &mut F, fn_name: &str) -> io::Result<()> {
219    writeln!(
220        f,
221        "#[allow(clippy::unreadable_literal)] pub fn {}() -> ::std::collections::HashMap<&'static str, ::static_files::Resource> {{",
222        fn_name
223    )
224}
225
226pub(crate) fn generate_function_end<F: Write>(f: &mut F) -> io::Result<()> {
227    writeln!(f, "}}")
228}
229
230pub(crate) fn generate_uses<F: Write>(f: &mut F) -> io::Result<()> {
231    writeln!(
232        f,
233        "use ::static_files::resource::new_resource as n;
234use ::std::include_bytes as i;",
235    )
236}
237
238pub(crate) fn generate_variable_header<F: Write>(f: &mut F, variable_name: &str) -> io::Result<()> {
239    writeln!(
240        f,
241        "let mut {} = ::std::collections::HashMap::new();",
242        variable_name
243    )
244}
245
246pub(crate) fn generate_variable_return<F: Write>(f: &mut F, variable_name: &str) -> io::Result<()> {
247    writeln!(f, "{}", variable_name)
248}