use cap_std::{ambient_authority, fs::Dir};
use clap::CommandFactory;
use clap_complete::aot::{Shell, generate_to};
use clap_mangen::Man;
use std::{
env,
path::{Path, PathBuf},
};
use time::{OffsetDateTime, format_description::well_known::Iso8601};
const FALLBACK_DATE: &str = "1970-01-01";
#[expect(
dead_code,
unused_imports,
reason = "shared library source; the unreached API is exercised by the library crate"
)]
#[path = "src/cli/build_support.rs"]
mod cli;
#[path = "src/cli_localization.rs"]
mod cli_localization;
#[expect(
dead_code,
reason = "shared library source; the unreached API is exercised by the library crate"
)]
#[path = "src/cli_l10n.rs"]
mod cli_l10n;
#[expect(
dead_code,
reason = "shared library source; the unreached API is exercised by the library crate"
)]
#[path = "src/host_pattern.rs"]
mod host_pattern;
#[path = "src/locale_catalogues.rs"]
pub mod locale_catalogues;
#[path = "src/localization/mod.rs"]
pub mod localization;
#[expect(
dead_code,
reason = "shared library source; the unreached API is exercised by the library crate"
)]
#[path = "src/output_mode.rs"]
mod output_mode;
#[expect(
dead_code,
reason = "shared library source; the unreached API is exercised by the library crate"
)]
#[path = "src/theme.rs"]
mod theme;
mod build_l10n_audit;
#[expect(
clippy::disallowed_methods,
reason = "SOURCE_DATE_EPOCH is the reproducible-builds contract: the build system supplies it to the build script's process, so there is no seam to inject it through"
)]
fn manual_date() -> String {
let Ok(raw) = env::var("SOURCE_DATE_EPOCH") else {
return FALLBACK_DATE.into();
};
let Ok(ts) = raw.parse::<i64>() else {
println!(
"cargo:warning=Invalid SOURCE_DATE_EPOCH '{raw}'; expected integer seconds since Unix epoch; falling back to {FALLBACK_DATE}"
);
return FALLBACK_DATE.into();
};
let Ok(dt) = OffsetDateTime::from_unix_timestamp(ts) else {
println!(
"cargo:warning=Invalid SOURCE_DATE_EPOCH '{raw}'; not a valid Unix timestamp; falling back to {FALLBACK_DATE}"
);
return FALLBACK_DATE.into();
};
dt.format(&Iso8601::DATE).unwrap_or_else(|_| {
println!(
"cargo:warning=Invalid SOURCE_DATE_EPOCH '{raw}'; formatting failed; falling back to {FALLBACK_DATE}"
);
FALLBACK_DATE.into()
})
}
#[expect(
clippy::disallowed_methods,
reason = "TARGET and PROFILE are set by Cargo for the build script alone; nothing else knows the triple and profile being built, so they cannot be passed in"
)]
fn out_dir_for_target_profile(artefact: &str) -> PathBuf {
let target = env::var("TARGET").unwrap_or_else(|_| "unknown-target".into());
let profile = env::var("PROFILE").unwrap_or_else(|_| "unknown-profile".into());
PathBuf::from(format!("target/{artefact}/{target}/{profile}"))
}
fn write_man_page(data: &[u8], dir: &Path, page_name: &str) -> std::io::Result<PathBuf> {
let man_dir = if dir.is_relative() {
let working_dir = Dir::open_ambient_dir(".", ambient_authority())?;
working_dir.create_dir_all(dir)?;
working_dir.open_dir(dir)?
} else {
Dir::open_ambient_dir(dir, ambient_authority())?
};
let temporary_name = format!("{page_name}.tmp");
man_dir.write(&temporary_name, data)?;
man_dir.rename(&temporary_name, &man_dir, page_name)?;
Ok(dir.join(page_name))
}
fn emit_rerun_directives() {
println!("cargo:rerun-if-changed=src/cli/build_support.rs");
println!("cargo:rerun-if-changed=src/cli/config.rs");
println!("cargo:rerun-if-changed=src/cli/help.rs");
println!("cargo:rerun-if-changed=src/cli/parser.rs");
println!("cargo:rerun-if-changed=src/cli/parsing.rs");
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
println!("cargo:rerun-if-env-changed=CARGO_PKG_DESCRIPTION");
println!("cargo:rerun-if-env-changed=CARGO_PKG_AUTHORS");
println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH");
println!("cargo:rerun-if-env-changed=TARGET");
println!("cargo:rerun-if-env-changed=PROFILE");
println!("cargo:rerun-if-changed=src/localization/keys.rs");
println!("cargo:rerun-if-changed=src/locale_catalogues.rs");
println!("cargo:rerun-if-changed=Cargo.toml");
for entry in locale_catalogues::SUPPORTED_LOCALES {
println!(
"cargo:rerun-if-changed={}",
build_l10n_audit::catalogue_path(entry.tag()).display()
);
}
}
#[expect(
clippy::disallowed_methods,
reason = "CARGO_PKG_VERSION and OUT_DIR are Cargo's own build-script inputs; they describe the crate being compiled and Cargo provides them only through the environment"
)]
fn generate_man_page(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let cmd = cli::Cli::command();
let name = cmd
.get_bin_name()
.unwrap_or_else(|| cmd.get_name())
.to_owned();
let version = env::var("CARGO_PKG_VERSION").map_err(
|_| "CARGO_PKG_VERSION must be set by Cargo; cannot render manual page without it.",
)?;
let man = Man::new(cmd)
.section("1")
.source(format!("{name} {version}"))
.date(manual_date());
let mut buf = Vec::new();
man.render(&mut buf)?;
let page_name = format!("{name}.1");
let destination = write_man_page(&buf, out_dir, &page_name)?;
println!(
"cargo:rustc-env=NETSUKE_GENERATED_MAN_PAGE={}",
destination.display()
);
if let Some(extra_dir) = env::var_os("OUT_DIR") {
let extra_dir_path = PathBuf::from(extra_dir);
if let Err(err) = write_man_page(&buf, &extra_dir_path, &page_name) {
println!(
"cargo:warning=Failed to stage manual page in OUT_DIR ({}): {err}",
extra_dir_path.display()
);
}
}
Ok(())
}
fn generate_completions(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let working_dir = Dir::open_ambient_dir(".", ambient_authority())?;
working_dir.create_dir_all(out_dir)?;
let cli_command = cli::Cli::command();
let name = cli_command
.get_bin_name()
.unwrap_or_else(|| cli_command.get_name())
.to_owned();
for shell in [
Shell::Bash,
Shell::Elvish,
Shell::Fish,
Shell::PowerShell,
Shell::Zsh,
] {
let mut completion_command = cli::Cli::command();
generate_to(shell, &mut completion_command, &name, out_dir)?;
}
println!(
"cargo:rustc-env=NETSUKE_GENERATED_COMPLETIONS_DIR={}",
out_dir.display()
);
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
emit_rerun_directives();
build_l10n_audit::audit_localization_keys()?;
generate_man_page(&out_dir_for_target_profile("generated-man"))?;
generate_completions(&out_dir_for_target_profile("generated-completions"))
}