use crate::{
Extension, diagnostics, discovery::discover, message_types, metadata, render,
settings::Settings, source, tree::Node, upstream,
};
use std::{
env, fs,
path::{Path, PathBuf},
process::ExitCode,
};
#[must_use = "return this exit code from build.rs main so generation failures stop the build"]
pub fn build() -> ExitCode {
diagnostics::build_outcome(from_cargo(), &mut std::io::stderr().lock())
}
#[must_use = "return this exit code from build.rs main"]
pub fn build_with(extension: &dyn Extension) -> ExitCode {
diagnostics::build_outcome(from_cargo_with(extension), &mut std::io::stderr().lock())
}
pub fn from_cargo() -> Result<(), String> {
cargo_generation(None)
}
pub fn from_cargo_with(extension: &dyn Extension) -> Result<(), String> {
cargo_generation(Some(extension))
}
fn cargo_generation(extension: Option<&dyn Extension>) -> Result<(), String> {
let package =
PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").ok_or("CARGO_MANIFEST_DIR is unset")?);
let output = PathBuf::from(env::var_os("OUT_DIR").ok_or("OUT_DIR is unset")?);
let manifest_path = package.join("Cargo.toml");
println!("cargo::rerun-if-changed={}", manifest_path.display());
let manifest = fs::read_to_string(&manifest_path).map_err(|error| error.to_string())?;
let settings = Settings::from_manifest(&manifest)?;
generate_inner(&package, &output, &settings, extension)
}
pub fn generate(package: &Path, output: &Path, settings: &Settings) -> Result<(), String> {
generate_inner(package, output, settings, None)
}
pub fn generate_with(
package: &Path,
output: &Path,
settings: &Settings,
extension: &dyn Extension,
) -> Result<(), String> {
generate_inner(package, output, settings, Some(extension))
}
fn generate_inner(
package: &Path,
output: &Path,
settings: &Settings,
extension: Option<&dyn Extension>,
) -> Result<(), String> {
if let Some(extension) = extension {
let filename = extension.filename();
let path = Path::new(filename);
if path.file_name().and_then(|name| name.to_str()) != Some(filename)
|| !filename.ends_with(".rs")
|| filename.contains(['\\', '#', ':'])
|| matches!(
filename,
"translations.rs" | "locale_modules.rs" | "validation.rs"
) {
return Err(format!("invalid extension output filename: {filename:?}"));
}
}
let package = package.canonicalize().map_err(|error| error.to_string())?;
let sources = discover(&package, settings)?;
let paths: Vec<_> = sources
.modules
.iter()
.filter(|module| module.language == sources.configuration.source_language)
.map(|module| module.path.clone())
.collect();
let tree = Node::build(&paths)?;
render::validate_extension(&tree, extension)?;
let config_path = settings.catalog_path(&package);
let root = &sources.root;
println!("cargo::rerun-if-changed={}", config_path.display());
println!("cargo::rerun-if-changed={}", root.display());
for module in &sources.modules {
println!("cargo::rerun-if-changed={}", module.absolute.display());
}
fs::create_dir_all(output).map_err(|error| error.to_string())?;
let output = output.canonicalize().map_err(|error| error.to_string())?;
fs::write(
output.join("locale_modules.rs"),
source::format(
metadata::render(settings, &sources, &config_path)?,
"locale_modules.rs",
)?,
)
.map_err(|error| error.to_string())?;
upstream::generate(&sources, &paths, &output)?;
let message_types = message_types::discover(&paths, &output)?;
message_types::validate(&tree, &message_types)?;
let validation = include_str!("schema.rs").replacen("//!", "//", 1).replacen(
"use fluent_syntax::{ast, parser};",
"use super::fluent_syntax::{ast, parser};",
1,
);
fs::write(output.join("validation.rs"), validation).map_err(|error| error.to_string())?;
fs::write(
output.join("translations.rs"),
source::format(
render::render(&tree, &sources, &message_types, None)?,
"translations.rs",
)?,
)
.map_err(|error| error.to_string())?;
if let Some(extension) = extension {
fs::write(
output.join(extension.filename()),
source::format(
render::render(&tree, &sources, &message_types, Some(extension))?,
extension.filename(),
)?,
)
.map_err(|error| error.to_string())?;
}
Ok(())
}