| @@ -4,6 +4,8 @@ use std::{ |
| pin::Pin, |
| }; |
| |
| +use sha2::{Digest, Sha256}; |
| + |
| use figment::{ |
| Figment, |
| providers::{Format, Toml}, |
| @@ -19,6 +21,21 @@ use crate::{changelog::ChangelogExtractor, config::AbbayeConfig}; |
| const TEMPLATE_ROOT_INDEX: &str = include_str!("templates/root_index.html"); |
| const TEMPLATE_VERSION_INDEX: &str = include_str!("templates/version_index.html"); |
| |
| +// ── Types ─────────────────────────────────────────────────────────────────── |
| + |
| +/// Metadata about a single file-type dist artifact, passed to Tera templates. |
| +#[derive(serde::Serialize)] |
| +struct DistFileInfo { |
| + /// File name (relative to `dist/`). |
| + name: String, |
| + /// Raw byte size of the file. |
| + size_bytes: u64, |
| + /// Human-readable size (e.g. "1.4 MB"). |
| + size_human: String, |
| + /// Lowercase hex-encoded SHA-256 digest of the file contents. |
| + sha256: String, |
| +} |
| + |
| // ── Public API ──────────────────────────────────────────────────────────────── |
| |
| /// Load the Abbaye2 configuration from the current working directory. |
| @@ -38,8 +55,8 @@ pub fn load_config() -> Result<AbbayeConfig> { |
| /// |
| /// # Steps |
| /// |
| -/// 1. Extract the current version via the configured [`AnyVersionExtractor`]. |
| -/// 2. Run every configured builder and split the resulting [`ArtifactPath`]s |
| +/// 1. Extract the current version via the configured [`crate::version_extractors::AnyVersionExtractor`]. |
| +/// 2. Run every configured builder and split the resulting [`crate::builders::ArtifactPath`]s |
| /// into *dist* (regular files) and *docs* (directories). |
| /// 3. Copy dist artifacts to `<output>/<version>/dist/`. |
| /// 4. Copy doc directories to `<output>/<version>/docs/<crate>/` and archive |
| @@ -96,8 +113,21 @@ pub async fn build_site(config: AbbayeConfig) -> Result<()> { |
| .into_diagnostic()?; |
| } |
| |
| - let dist_file_names: Vec<String> = dist_artifacts.iter().map(|a| a.name.clone()).collect(); |
| - let has_dist = !dist_file_names.is_empty(); |
| + // Compute size + SHA-256 for each copied dist artifact. |
| + let mut dist_file_infos: Vec<DistFileInfo> = Vec::new(); |
| + for artifact in &dist_artifacts { |
| + let dest = dist_dir.join(&artifact.name); |
| + let bytes = tokio::fs::read(&dest).await.into_diagnostic()?; |
| + let size_bytes = bytes.len() as u64; |
| + let sha256 = hex_sha256(&bytes); |
| + dist_file_infos.push(DistFileInfo { |
| + name: artifact.name.clone(), |
| + size_bytes, |
| + size_human: human_size(size_bytes), |
| + sha256, |
| + }); |
| + } |
| + let has_dist = !dist_file_infos.is_empty(); |
| |
| // ── 5. Lay out docs/ ────────────────────────────────────────────────────── |
| let has_docs = !doc_artifacts.is_empty(); |
| @@ -168,7 +198,7 @@ pub async fn build_site(config: AbbayeConfig) -> Result<()> { |
| version_ctx.insert("has_docs", &has_docs); |
| version_ctx.insert("has_docs_tarball", &has_docs_tarball); |
| version_ctx.insert("has_dist", &has_dist); |
| - version_ctx.insert("dist_files", &dist_file_names); |
| + version_ctx.insert("dist_files", &dist_file_infos); |
| |
| let version_html = tera |
| .render("version_index.html", &version_ctx) |
| @@ -321,6 +351,33 @@ fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering { |
| } |
| } |
| |
| +/// Compute a lowercase hex-encoded SHA-256 digest of `data`. |
| +fn hex_sha256(data: &[u8]) -> String { |
| + let mut hasher = Sha256::new(); |
| + hasher.update(data); |
| + hasher |
| + .finalize() |
| + .iter() |
| + .map(|b| format!("{b:02x}")) |
| + .collect() |
| +} |
| + |
| +/// Format a byte count as a human-readable string (e.g. "1.4 MB"). |
| +fn human_size(bytes: u64) -> String { |
| + const KIB: u64 = 1024; |
| + const MIB: u64 = KIB * 1024; |
| + const GIB: u64 = MIB * 1024; |
| + if bytes >= GIB { |
| + format!("{:.1} GB", bytes as f64 / GIB as f64) |
| + } else if bytes >= MIB { |
| + format!("{:.1} MB", bytes as f64 / MIB as f64) |
| + } else if bytes >= KIB { |
| + format!("{:.1} KB", bytes as f64 / KIB as f64) |
| + } else { |
| + format!("{bytes} B") |
| + } |
| +} |
| + |
| /// Create or replace the `latest` symlink in `output_dir`, pointing to |
| /// `version_dir_name`. |
| /// |