use std::{env, fmt, fs, io, process};
use std::path::{PathBuf, Path};
const UI_DIR: &str = "ui";
const INDEX_PATH: &str = "ui/index.html";
const ASSETS_DIR: &str = "ui/assets";
const RS_FILE: &str = "ui_assets.rs";
const TYPES: &[(&str, &str)] = &[
("css", "text/css"),
("html", "text/html"),
("ico", "image/x-icon"),
("js", "text/javascript"),
("svg", "image/svg+xml"),
("woff", "font/woff"),
("woff2", "font/woff2"),
];
struct Asset {
path: PathBuf,
media_type: &'static str,
content: Vec<u8>,
}
impl Asset {
fn load(path: PathBuf, asset: bool) -> Result<Self, String> {
let path_ext = match path.extension().and_then(|s| s.to_str()) {
Some(ext) => ext,
None => {
return Err(format!(
"Asset without extension: '{}'", path.display()
))
}
};
let media_type = match TYPES.iter().find_map(|(ext, media_type)| {
(path_ext == *ext).then_some(*media_type)
}) {
Some(media) => media,
None => {
return Err(format!(
"Asset with unknown extension '{path_ext}'"
))
}
};
Ok(Self {
path: if asset {
path.strip_prefix(ASSETS_DIR).map_err(|_| {
format!("Asset path {} not under {}",
path.display(), ASSETS_DIR
)
})?.into()
}
else {
INDEX_PATH.into()
},
media_type,
content: fs::read(&path).map_err(|err| {
format!(
"Failed to read UI asset file {}: {}.",
path.display(), err
)
})?
})
}
}
impl fmt::Display for Asset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"
Asset {{
path: r#\"{}\"#,
media_type: \"{}\",
content: &{:?},
}}
",
self.path.display(),
self.media_type,
self.content.as_slice(),
)
}
}
#[derive(Default)]
struct Assets(Vec<Asset>);
impl Assets {
fn load_dir(&mut self, path: PathBuf) -> Result<(), String> {
let dir = fs::read_dir(&path).map_err(|err| {
format!("Failed to open directory {}: {}", path.display(), err)
})?;
for entry in dir {
let entry = entry.map_err(|err| {
format!("Failed to read directory {}: {}", path.display(), err)
})?;
let path = entry.path();
if path.is_dir() {
self.load_dir(path)?;
}
else {
self.0.push(Asset::load(path, true)?)
}
}
Ok(())
}
}
fn write_mod(
index: Asset, assets: Assets, dest: &mut impl io::Write
) -> Result<(), io::Error> {
write!(dest,
r#"
pub struct Asset {{
pub path: &'static str,
pub media_type: &'static str,
pub content: &'static [u8],
}}
pub static INDEX: Asset = {index};
pub static ASSETS: &[Asset] = &[
"#
)?;
for item in assets.0 {
write!(dest, "{item},")?;
}
writeln!(dest, "];")
}
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap_or_default();
let target_path = Path::new(&out_dir).join(RS_FILE);
let mut target = match fs::File::create(&target_path) {
Ok(target) => io::BufWriter::new(target),
Err(err) => {
eprintln!("Failed to open assets module file {}: {}",
target_path.display(), err
);
process::exit(1);
}
};
let index = match Asset::load(INDEX_PATH.into(), false) {
Ok(index) => index,
Err(err) => {
eprintln!("{err}");
process::exit(1);
}
};
let mut assets = Assets::default();
if let Err(err) = assets.load_dir(ASSETS_DIR.into()) {
eprintln!("{err}");
process::exit(1);
}
if let Err(err) = write_mod(index, assets, &mut target) {
eprintln!("Failed to write to assets module file {}: {}",
target_path.display(), err
);
process::exit(1)
}
println!("cargo:rerun-if-changed={UI_DIR}");
}