1use std::{
5 fs::{self, File, Metadata},
6 io::{self, Write},
7 path::{Path, PathBuf},
8 time::SystemTime,
9};
10
11use path_slash::PathExt;
12
13pub struct Resource {
15 pub data: &'static [u8],
16 pub modified: u64,
17 pub mime_type: &'static str,
18}
19
20#[inline]
22#[must_use]
23pub fn new_resource(data: &'static [u8], modified: u64, mime_type: &'static str) -> Resource {
24 Resource {
25 data,
26 modified,
27 mime_type,
28 }
29}
30
31pub(crate) const DEFAULT_VARIABLE_NAME: &str = "r";
32
33pub fn generate_resources<P: AsRef<Path>, G: AsRef<Path>>(
59 project_dir: P,
60 filter: Option<fn(p: &Path) -> bool>,
61 generated_filename: G,
62 fn_name: &str,
63) -> io::Result<()> {
64 let resources = collect_resources(&project_dir, filter)?;
65
66 let mut f = File::create(&generated_filename)?;
67
68 generate_function_header(&mut f, fn_name)?;
69 generate_uses(&mut f)?;
70
71 generate_variable_header(&mut f, DEFAULT_VARIABLE_NAME)?;
72 generate_resource_inserts(&mut f, &project_dir, DEFAULT_VARIABLE_NAME, &resources)?;
73 generate_variable_return(&mut f, DEFAULT_VARIABLE_NAME)?;
74
75 generate_function_end(&mut f)?;
76
77 Ok(())
78}
79
80pub fn generate_resources_mapping<P: AsRef<Path>, G: AsRef<Path>>(
114 project_dir: P,
115 filter: Option<fn(p: &Path) -> bool>,
116 generated_filename: G,
117) -> io::Result<()> {
118 let resources = collect_resources(&project_dir, filter)?;
119
120 let mut f = File::create(&generated_filename)?;
121 writeln!(f, "{{")?;
122
123 generate_uses(&mut f)?;
124
125 generate_variable_header(&mut f, DEFAULT_VARIABLE_NAME)?;
126
127 generate_resource_inserts(&mut f, &project_dir, DEFAULT_VARIABLE_NAME, &resources)?;
128
129 generate_variable_return(&mut f, DEFAULT_VARIABLE_NAME)?;
130
131 writeln!(f, "}}")?;
132 Ok(())
133}
134
135#[cfg(not(feature = "sort"))]
136pub(crate) fn collect_resources<P: AsRef<Path>>(
137 path: P,
138 filter: Option<fn(p: &Path) -> bool>,
139) -> io::Result<Vec<(PathBuf, Metadata)>> {
140 collect_resources_nested(path, filter)
141}
142
143#[cfg(feature = "sort")]
144pub(crate) fn collect_resources<P: AsRef<Path>>(
145 path: P,
146 filter: Option<fn(p: &Path) -> bool>,
147) -> io::Result<Vec<(PathBuf, Metadata)>> {
148 let mut resources = collect_resources_nested(path, filter)?;
149 resources.sort_by(|a, b| a.0.cmp(&b.0));
150 Ok(resources)
151}
152
153#[inline]
154fn collect_resources_nested<P: AsRef<Path>>(
155 path: P,
156 filter: Option<fn(p: &Path) -> bool>,
157) -> io::Result<Vec<(PathBuf, Metadata)>> {
158 let mut result = vec![];
159
160 for entry in fs::read_dir(&path)? {
161 let entry = entry?;
162 let path = entry.path();
163
164 if let Some(ref filter) = filter {
165 if !filter(path.as_ref()) {
166 continue;
167 }
168 }
169
170 if path.is_dir() {
171 let nested = collect_resources(path, filter)?;
172 result.extend(nested);
173 } else {
174 result.push((path, entry.metadata()?));
175 }
176 }
177
178 Ok(result)
179}
180
181pub(crate) fn generate_resource_inserts<P: AsRef<Path>, W: Write>(
182 f: &mut W,
183 project_dir: &P,
184 variable_name: &str,
185 resources: &[(PathBuf, Metadata)],
186) -> io::Result<()> {
187 for resource in resources {
188 generate_resource_insert(f, project_dir, variable_name, resource)?;
189 }
190 Ok(())
191}
192
193pub(crate) fn generate_resource_insert<P: AsRef<Path>, W: Write>(
194 f: &mut W,
195 project_dir: &P,
196 variable_name: &str,
197 resource: &(PathBuf, Metadata),
198) -> io::Result<()> {
199 let (path, metadata) = resource;
200 let abs_path = path.canonicalize()?;
201 let key_path = path.strip_prefix(project_dir).unwrap().to_slash().unwrap();
202
203 let modified = if let Ok(Ok(modified)) = metadata
204 .modified()
205 .map(|x| x.duration_since(SystemTime::UNIX_EPOCH))
206 {
207 modified.as_secs()
208 } else {
209 0
210 };
211 let mime_type = mime_guess::MimeGuess::from_path(path).first_or_octet_stream();
212 writeln!(
213 f,
214 "{variable_name}.insert({key_path:?},n(i!({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)] #[must_use] pub fn {fn_name}() -> ::std::collections::HashMap<&'static str, ::static_files::Resource> {{",
222 )
223}
224
225pub(crate) fn generate_function_end<F: Write>(f: &mut F) -> io::Result<()> {
226 writeln!(f, "}}")
227}
228
229pub(crate) fn generate_uses<F: Write>(f: &mut F) -> io::Result<()> {
230 writeln!(
231 f,
232 "\
233use ::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 {variable_name} = ::std::collections::HashMap::new();",
242 )
243}
244
245pub(crate) fn generate_variable_return<F: Write>(f: &mut F, variable_name: &str) -> io::Result<()> {
246 writeln!(f, "{variable_name}")
247}