1use path_slash::PathExt;
5use std::{
6 fs::{self, File, Metadata},
7 io::{self, Write},
8 path::{Path, PathBuf},
9 time::SystemTime,
10};
11
12pub struct Resource {
14 pub data: &'static [u8],
15 pub modified: u64,
16 pub mime_type: &'static str,
17}
18
19#[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
31pub 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
78pub 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}