Files
novem/build.rs

201 lines
7.1 KiB
Rust

use std::collections::{BTreeMap, BTreeSet};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug)]
struct AssetFile {
virtual_path: String,
disk_path: PathBuf,
}
fn main() {
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("missing CARGO_MANIFEST_DIR"));
let asset_root = manifest_dir.join("src/asset");
let fixture_root = manifest_dir.join("fixtures/github");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("missing OUT_DIR"));
let asset_out_file = out_dir.join("asset.rs");
let fixture_out_file = out_dir.join("github_fixtures.rs");
println!("cargo::rerun-if-changed={}", asset_root.display());
println!("cargo::rerun-if-changed={}", fixture_root.display());
let mut directory_entries = BTreeMap::<String, BTreeSet<String>>::new();
directory_entries
.entry(String::new())
.or_default()
.insert(String::from("asset"));
let mut asset_files = Vec::new();
collect_assets(
&asset_root,
"asset",
&mut directory_entries,
&mut asset_files,
);
let generated = render_assets(&asset_files, &directory_entries);
let fixture_module = render_github_fixtures(&fixture_root);
fs::write(asset_out_file, generated).expect("failed to write generated assets module");
fs::write(fixture_out_file, fixture_module)
.expect("failed to write generated github fixtures module");
}
fn collect_assets(
disk_dir: &Path,
virtual_dir: &str,
directory_entries: &mut BTreeMap<String, BTreeSet<String>>,
asset_files: &mut Vec<AssetFile>,
) {
let mut entries = fs::read_dir(disk_dir)
.unwrap_or_else(|err| panic!("failed to read {}: {err}", disk_dir.display()))
.map(|entry| entry.expect("failed to read directory entry"))
.collect::<Vec<_>>();
entries.sort_by_key(|entry| entry.file_name());
for entry in entries {
let file_name = entry
.file_name()
.into_string()
.unwrap_or_else(|_| panic!("non-utf8 asset name in {}", disk_dir.display()));
let child_virtual_path = format!("{virtual_dir}/{file_name}");
let path = entry.path();
directory_entries
.entry(String::from(virtual_dir))
.or_default()
.insert(file_name);
if path.is_dir() {
collect_assets(&path, &child_virtual_path, directory_entries, asset_files);
} else if path.is_file() {
asset_files.push(AssetFile {
virtual_path: child_virtual_path,
disk_path: path,
});
}
}
}
fn render_assets(
asset_files: &[AssetFile],
directory_entries: &BTreeMap<String, BTreeSet<String>>,
) -> String {
let mut output = String::new();
output.push_str(
"pub fn load_asset(path: &str) -> gpui::Result<Option<std::borrow::Cow<'static, [u8]>>> {\n",
);
output.push_str(" match path {\n");
for file in asset_files {
output.push_str(" ");
output.push_str(&string_literal(&file.virtual_path));
output.push_str(" => Ok(Some(std::borrow::Cow::Borrowed(include_bytes!(");
output.push_str(&string_literal(&file.disk_path.to_string_lossy()));
output.push_str(")))),\n");
}
output.push_str(" _ => Err(anyhow::anyhow!(\"asset not found: {path}\")),\n");
output.push_str(" }\n");
output.push_str("}\n\n");
output.push_str("pub fn list_assets(path: &str) -> gpui::Result<Vec<gpui::SharedString>> {\n");
output.push_str(" let normalized = path.trim_end_matches('/');\n");
output.push_str(" let normalized = if normalized == \".\" { \"\" } else { normalized };\n");
output.push_str(" let entries: &[&str] = match normalized {\n");
for (directory, entries) in directory_entries {
output.push_str(" ");
output.push_str(&string_literal(directory));
output.push_str(" => &[\n");
for entry in entries {
output.push_str(" ");
output.push_str(&string_literal(entry));
output.push_str(",\n");
}
output.push_str(" ],\n");
}
output.push_str(" _ => &[],\n");
output.push_str(" };\n");
output.push_str(" Ok(entries.iter().copied().map(gpui::SharedString::from).collect())\n");
output.push_str("}\n");
output
}
fn render_github_fixtures(fixture_root: &Path) -> String {
let user_fetch = read_json_fixture(&fixture_root.join("user.fetch.json"));
let repo_list = read_json_fixture(&fixture_root.join("repo.list.json"));
let mut issue_fixtures = BTreeMap::<(String, u32), String>::new();
let mut entries = fs::read_dir(fixture_root)
.unwrap_or_else(|err| panic!("failed to read {}: {err}", fixture_root.display()))
.map(|entry| entry.expect("failed to read github fixture entry"))
.collect::<Vec<_>>();
entries.sort_by_key(|entry| entry.file_name());
for entry in entries {
let file_name = entry
.file_name()
.into_string()
.unwrap_or_else(|_| panic!("non-utf8 fixture name in {}", fixture_root.display()));
let Some((filter, page)) = parse_issue_fixture_name(&file_name) else {
continue;
};
issue_fixtures.insert((filter, page), read_json_fixture(&entry.path()));
}
let mut output = String::new();
output.push_str("pub fn user_fetch() -> &'static str {\n");
output.push_str(" ");
output.push_str(&string_literal(&user_fetch));
output.push_str("\n}\n\n");
output.push_str("pub fn repo_list() -> &'static str {\n");
output.push_str(" ");
output.push_str(&string_literal(&repo_list));
output.push_str("\n}\n\n");
output.push_str("pub fn issues_pull_requests(filter: &str, page: u32) -> Option<&'static str> {\n");
output.push_str(" match (filter, page) {\n");
for ((filter, page), json) in issue_fixtures {
output.push_str(" (");
output.push_str(&string_literal(&filter));
output.push_str(", ");
output.push_str(&page.to_string());
output.push_str(") => Some(");
output.push_str(&string_literal(&json));
output.push_str("),\n");
}
output.push_str(" _ => None,\n");
output.push_str(" }\n");
output.push_str("}\n");
output
}
fn read_json_fixture(path: &Path) -> String {
let raw = fs::read_to_string(path)
.unwrap_or_else(|err| panic!("failed to read fixture {}: {err}", path.display()));
let value: serde_json::Value = serde_json::from_str(&raw)
.unwrap_or_else(|err| panic!("invalid json fixture {}: {err}", path.display()));
serde_json::to_string(&value)
.unwrap_or_else(|err| panic!("failed to serialize fixture {}: {err}", path.display()))
}
fn parse_issue_fixture_name(file_name: &str) -> Option<(String, u32)> {
let name = file_name.strip_suffix(".json")?;
let rest = name.strip_prefix("issues.pull_requests.")?;
let (filter, page) = rest.rsplit_once(".page")?;
let page = page.parse::<u32>().ok()?;
Some((filter.to_owned(), page))
}
fn string_literal(value: &str) -> String {
format!("{value:?}")
}