use std::fs;
use std::path::{Path, PathBuf};
const EXT_EXCLUDED: &[&str] = &[".vscodeignore", ".gitignore", "Makefile", ".vscode"];
fn main() {
let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR"));
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let mut gerado = String::from("// Gerado por build.rs — não edite.\n\n");
gerado.push_str(&emitir_templates(&manifest.join("templates")));
gerado.push_str(&emitir_extensoes(&manifest));
gerado.push_str(&emitir_versao_do_motor(&manifest));
fs::write(out_dir.join("embedded.rs"), gerado).expect("gravar embedded.rs");
}
fn emitir_templates(raiz: &Path) -> String {
let mut arquivos = Vec::new();
coletar(raiz, raiz, &mut arquivos, &[]);
arquivos.sort();
let mut s = String::from("pub static TEMPLATES: &[(&str, &[u8])] = &[\n");
for (rel, abs) in &arquivos {
println!("cargo:rerun-if-changed={}", abs.display());
s.push_str(&format!(" ({:?}, include_bytes!({:?})),\n", rel, abs));
}
s.push_str("];\n\n");
s
}
fn emitir_extensoes(manifest: &Path) -> String {
let raiz = localizar_extensoes(manifest);
let dirs: Vec<PathBuf> = match &raiz {
Some(r) => {
let mut v: Vec<PathBuf> = fs::read_dir(r)
.map(|it| {
it.flatten()
.map(|e| e.path())
.filter(|p| p.join("package.json").is_file())
.collect()
})
.unwrap_or_default();
v.sort();
v
}
None => Vec::new(),
};
let mut listas = String::new();
let mut tabela = String::from("pub static EXTENSOES: &[Extensao] = &[\n");
for (i, dir) in dirs.iter().enumerate() {
let mut arquivos = Vec::new();
coletar(dir, dir, &mut arquivos, EXT_EXCLUDED);
arquivos.sort();
let ident = format!("EXT_{i}");
listas.push_str(&format!("static {ident}: &[(&str, &[u8])] = &[\n"));
for (rel, abs) in &arquivos {
println!("cargo:rerun-if-changed={}", abs.display());
listas.push_str(&format!(" ({:?}, include_bytes!({:?})),\n", rel, abs));
}
listas.push_str("];\n\n");
let nome = dir.file_name().unwrap_or_default().to_string_lossy();
tabela.push_str(&format!(
" Extensao {{ origem: {nome:?}, arquivos: {ident} }},\n"
));
}
tabela.push_str("];\n\n");
format!("{listas}{tabela}")
}
fn localizar_extensoes(manifest: &Path) -> Option<PathBuf> {
let vendored = manifest.join("extensions");
let checkout = manifest.join("../../editors");
println!("cargo:rerun-if-changed={}", vendored.display());
println!("cargo:rerun-if-changed={}", checkout.display());
if vendored.is_dir() {
return Some(vendored);
}
if checkout.is_dir() {
return Some(checkout);
}
println!(
"cargo:warning=nenhuma árvore de extensões encontrada (nem `extensions/`, nem `../../editors/`): \
a CLI será compilada sem extensões embutidas"
);
None
}
fn emitir_versao_do_motor(manifest: &Path) -> String {
let cargo_raiz = manifest.join("../../Cargo.toml");
println!("cargo:rerun-if-changed={}", cargo_raiz.display());
let versao = fs::read_to_string(&cargo_raiz)
.ok()
.and_then(|txt| {
txt.lines()
.take_while(|l| !l.starts_with("[dependencies]"))
.find_map(|l| {
let v = l.strip_prefix("version")?.trim_start().strip_prefix('=')?;
Some(v.trim().trim_matches('"').to_string())
})
})
.unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
let mut partes = versao.split('.');
let curta = match (partes.next(), partes.next()) {
(Some(maior), Some(menor)) => format!("{maior}.{menor}"),
_ => versao,
};
format!("pub const VERSAO_MOTOR: &str = {curta:?};\n")
}
fn coletar(base: &Path, dir: &Path, saida: &mut Vec<(String, PathBuf)>, excluir: &[&str]) {
let Ok(entradas) = fs::read_dir(dir) else {
return;
};
for entrada in entradas.flatten() {
let caminho = entrada.path();
let nome = entrada.file_name().to_string_lossy().to_string();
if excluir.contains(&nome.as_str()) || nome.ends_with(".vsix") {
continue;
}
if caminho.is_dir() {
coletar(base, &caminho, saida, excluir);
} else if let Ok(rel) = caminho.strip_prefix(base) {
let rel = rel.to_string_lossy().replace('\\', "/");
saida.push((rel, caminho.clone()));
}
}
}