Commit browse files
Message
Changed Files (12)
-
modified CHANGELOG.md
diff --git a/CHANGELOG.md b/CHANGELOG.md index 07cf56a..9436abc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## [unreleased] +## [0.1.1] - 2026-05-31 ### 🚀 Features @@ -9,3 +9,7 @@ ### 🐛 Bug Fixes - Remove source tarball and change archiving path + +### 🚜 Refactor + +- Rename abbaye2 to abbaye -
modified Cargo.lock
diff --git a/Cargo.lock b/Cargo.lock index 76d5813..6ef27f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,11 +3,12 @@ version = 4 [[package]] -name = "abbaye2" -version = "0.1.0" +name = "abbaye" +version = "0.1.1" dependencies = [ "figment", "flate2", + "globset", "ignore", "miette", "pulldown-cmark", -
modified Cargo.toml
diff --git a/Cargo.toml b/Cargo.toml index af12457..b2a2249 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,17 @@ [package] -name = "abbaye2" -version = "0.1.0" +name = "abbaye" +version = "0.1.1" authors = ["ololduck <ololduck@vit.am>"] license = "AGPL-3.0-or-later" edition = "2024" publish = false +include = ["CHANGELOG.md", "README.md", "src/**/*"] +rust-version = "1.85.1" [dependencies] figment = { version = "0.10.19", features = ["env", "toml"] } flate2 = "1" +globset = "0.4" ignore = "0.4" miette = { version = "7.6.0", features = ["fancy", "serde"] } pulldown-cmark = "0.12" @@ -23,3 +26,10 @@ tokio = { version = "1.52.3", features = ["full"] } toml = { version = "1.1.2", features = ["serde"] } tracing = { version = "0.1.44", features = ["log", "log-always"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[profile.release] +opt-level = "z" +strip = true +lto = true +panic = "abort" +codegen-units = 1 -
modified abbaye.toml
diff --git a/abbaye.toml b/abbaye.toml index 5aef144..e836b26 100644 --- a/abbaye.toml +++ b/abbaye.toml @@ -1,17 +1,19 @@ [site] -name = "Abbaye2" +name = "Abbaye" [version_extractor] -type = "cargo" +type = "git" +tag_prefix = "v" [changelog] -[[builders]] +[[builders]] # builds the project using cargo build --release type = "cargo" +targets = ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"] -[[builders]] +[[builders]] # generates documentation using cargo doc type = "cargo_doc" no_deps = true [[builders]] -type = "archive" +type = "archive" # creates a compressed tarball of the source code (can be of anything, really) -
modified mise.toml
diff --git a/mise.toml b/mise.toml index 43f3816..b024459 100644 --- a/mise.toml +++ b/mise.toml @@ -68,3 +68,22 @@ run = [ "release-plz release" ] sources = ["Cargo.toml", "Cargo.lock", "*.rs", "**/*.rs"] +depends_post = ["deploy-docs"] + +[tasks.clean] +description = "cleans the build output" +run = ["cargo clean", "rm -rf public/"] + +[tasks.abbaye] +wait_for = ["release", "clean"] +description = "builds the release website" +run = ["cargo run --bin abbaye"] +outputs = ["public/"] + +[tasks.deploy-docs] +description = "deploys documentation to https://vit.am/~ololduck/abbaye" +depends = ["abbaye"] +run = [ + "rsync --progress -avz --links --perms --update public/ ololduck@vit.am:public_html/abbaye/" +] +sources = ["public/"] -
modified src/builders/archive.rs
diff --git a/src/builders/archive.rs b/src/builders/archive.rs index 8b4b410..8793ed2 100644 --- a/src/builders/archive.rs +++ b/src/builders/archive.rs @@ -4,14 +4,19 @@ use std::{ }; use flate2::{Compression, write::GzEncoder}; +use globset::{Glob, GlobSet, GlobSetBuilder}; use ignore::WalkBuilder; use miette::{IntoDiagnostic, Result}; use serde::Deserialize; use crate::builders::{ArtifactPath, Builder}; +fn default_ignore_patterns() -> Vec<String> { + vec![".git".to_owned(), "*.local".to_owned()] +} + /// Configuration for [`ArchiveBuilder`]. -#[derive(Debug, Default, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct ArchiveBuilderConfig { /// Root directory to archive. Defaults to the current working directory. pub source_dir: Option<PathBuf>, @@ -25,6 +30,25 @@ pub struct ArchiveBuilderConfig { /// `myproject-1.0.0/src/main.rs`. /// Defaults to the source directory's name. pub prefix: Option<String>, + + /// Glob patterns for files and directories to exclude from the archive. + /// Each pattern is matched against every component of a path, so a pattern + /// like `".git"` excludes the `.git` directory and all its contents, and + /// `"*.local"` excludes any entry whose name ends with `.local`. + /// Defaults to `[".git", "*.local"]`. + #[serde(default = "default_ignore_patterns")] + pub ignore_patterns: Vec<String>, +} + +impl Default for ArchiveBuilderConfig { + fn default() -> Self { + Self { + source_dir: None, + output: None, + prefix: None, + ignore_patterns: default_ignore_patterns(), + } + } } /// Creates a `.tar.gz` archive of the source tree, honouring all `.gitignore` @@ -52,10 +76,13 @@ impl Builder for ArchiveBuilder { .unwrap_or_else(|| "source".to_owned()) }); - let archive_path = - tokio::task::spawn_blocking(move || create_archive(&source_dir, &output, &prefix)) - .await - .into_diagnostic()??; + let ignore_set = build_ignore_set(&config.ignore_patterns)?; + + let archive_path = tokio::task::spawn_blocking(move || { + create_archive(&source_dir, &output, &prefix, &ignore_set) + }) + .await + .into_diagnostic()??; let name = archive_path .file_name() @@ -72,10 +99,29 @@ impl Builder for ArchiveBuilder { } } +/// Compiles a [`GlobSet`] from the given list of glob patterns. +fn build_ignore_set(patterns: &[String]) -> Result<GlobSet> { + let mut builder = GlobSetBuilder::new(); + for pattern in patterns { + builder.add(Glob::new(pattern).into_diagnostic()?); + } + builder.build().into_diagnostic() +} + /// Walks `source_dir` respecting `.gitignore` rules and writes a `.tar.gz` /// archive to `output`, prefixing every entry with `prefix`. -fn create_archive(source_dir: &Path, output: &Path, prefix: &str) -> Result<PathBuf> { +/// `.git` and the output file itself are always excluded. Additionally, entries +/// whose path contains a component matched by `ignore_set` are skipped. +fn create_archive( + source_dir: &Path, + output: &Path, + prefix: &str, + ignore_set: &GlobSet, +) -> Result<PathBuf> { let file = File::create(output).into_diagnostic()?; + // Canonicalize now that the file exists so we can reliably detect it during + // the walk and avoid embedding the archive inside itself. + let output_canonical = output.canonicalize().into_diagnostic()?; let encoder = GzEncoder::new(file, Compression::default()); let mut archive = tar::Builder::new(encoder); @@ -86,8 +132,23 @@ fn create_archive(source_dir: &Path, output: &Path, prefix: &str) -> Result<Path let entry = result.into_diagnostic()?; let path = entry.path(); - // Never descend into the .git directory. - if path.components().any(|c| c.as_os_str() == ".git") { + let relative = path.strip_prefix(source_dir).into_diagnostic()?; + + // Always exclude .git, regardless of ignore_patterns. + if relative.components().any(|c| c.as_os_str() == ".git") { + continue; + } + + // Always exclude the output archive itself to prevent a tarbomb. + if path == output_canonical { + continue; + } + + // Skip entries whose path contains a component matched by the ignore set. + if relative + .components() + .any(|c| ignore_set.is_match(Path::new(c.as_os_str()))) + { continue; } @@ -95,7 +156,6 @@ fn create_archive(source_dir: &Path, output: &Path, prefix: &str) -> Result<Path continue; } - let relative = path.strip_prefix(source_dir).into_diagnostic()?; let entry_path = Path::new(prefix).join(relative); archive -
modified src/config.rs
diff --git a/src/config.rs b/src/config.rs index ec9838d..d594db1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,8 +16,35 @@ pub struct SiteConfig { pub readme: Option<PathBuf>, } +/// A full configuration for the Abbaye site generator. +/// +/// Here's a sample configuration that works well as a starting point for rust projects: +/// +/// ```toml +/// [site] +/// name = "Abbaye" +/// +/// [version_extractor] +/// type = "cargo" # extract version from Cargo.toml +/// +/// [changelog] +/// # let's use the default changelog extractor +/// +/// [[builders]] +/// type = "cargo" # calls `cargo build --release` for each target +/// targets = ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"] +/// [[builders]] +/// type = "cargo_doc" # generates documentation using `cargo doc` +/// no_deps = true +/// +/// [[builders]] +/// type = "archive" # creates a compressed tarball of the source code +/// ``` +/// +/// You can learn more about each builder type in the [builders module documentation](crate::builders). +/// #[derive(Debug, Deserialize)] -pub struct Abbaye2Config { +pub struct AbbayeConfig { pub site: SiteConfig, pub version_extractor: AnyVersionExtractor, pub changelog: ChangelogConfig, -
modified src/main.rs
diff --git a/src/main.rs b/src/main.rs index ac5becd..04555cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,10 +30,15 @@ use miette::Result; +/// All builders for the site (ex: cargo build, cargo doc, etc.). pub mod builders; +/// Parses the changelog file and generates a changelog page for the site. pub mod changelog; +/// Handles the `abbaye.toml` configuration file. pub mod config; +/// Generates the site from the configuration and builds it. pub mod site; +/// Extracts current version information from different sources (ex: git tags, cargo metadata, etc.). pub mod version_extractors; #[tokio::main] -
modified src/site.rs
diff --git a/src/site.rs b/src/site.rs index 45c8027..44010a3 100644 --- a/src/site.rs +++ b/src/site.rs @@ -14,7 +14,7 @@ use pulldown_cmark::{Options, Parser, html}; use tera::{Context, Tera}; use tracing::warn; -use crate::{changelog::ChangelogExtractor, config::Abbaye2Config}; +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"); @@ -25,7 +25,7 @@ const TEMPLATE_VERSION_INDEX: &str = include_str!("templates/version_index.html" /// /// Looks for `.abbaye.toml` first, then `abbaye.toml`; when both are present /// `abbaye.toml` takes precedence (last merge wins). -pub fn load_config() -> Result<Abbaye2Config> { +pub fn load_config() -> Result<AbbayeConfig> { let cwd = std::env::current_dir().into_diagnostic()?; Figment::new() .merge(Toml::file(cwd.join(".abbaye.toml"))) @@ -49,7 +49,7 @@ pub fn load_config() -> Result<Abbaye2Config> { /// 6. Re-render the root `<output>/index.html`, listing all known versions /// (newest first). /// 7. Update the `<output>/latest` symlink (Unix) or redirect page (other). -pub async fn build_site(config: Abbaye2Config) -> Result<()> { +pub async fn build_site(config: AbbayeConfig) -> Result<()> { let output_dir = config .output_dir .clone() @@ -178,9 +178,9 @@ pub async fn build_site(config: Abbaye2Config) -> Result<()> { .into_diagnostic()?; // ── 9. Root index.html ──────────────────────────────────────────────────── - // Collect every version that already has an index.html in the output dir, - // then merge in the current version and sort newest-first. - let mut versions = list_existing_versions(&output_dir).await?; + // Collect every known version from the version extractor, ensure the + // current version is present (handles untagged builds), then sort newest-first. + let mut versions = config.version_extractor.extract_all().await?; if !versions.contains(&version) { versions.push(version.clone()); } @@ -305,24 +305,6 @@ fn archive_dir(src: &Path, dest: &Path) -> Result<()> { Ok(()) } -/// Scan `output_dir` for subdirectories that contain an `index.html`, -/// excluding the `latest` symlink/directory. -async fn list_existing_versions(output_dir: &Path) -> Result<Vec<String>> { - let mut versions = Vec::new(); - let mut entries = tokio::fs::read_dir(output_dir).await.into_diagnostic()?; - while let Some(entry) = entries.next_entry().await.into_diagnostic()? { - let name = entry.file_name().to_string_lossy().into_owned(); - if name == "latest" { - continue; - } - let path = entry.path(); - if path.is_dir() && path.join("index.html").exists() { - versions.push(name); - } - } - Ok(versions) -} - /// Compare two version strings, preferring semver ordering and falling back /// to lexicographic comparison for non-semver strings (e.g. git describe output). fn strip_v(s: &str) -> &str { -
modified src/version_extractors/cargo.rs
diff --git a/src/version_extractors/cargo.rs b/src/version_extractors/cargo.rs index bf13a26..97de99f 100644 --- a/src/version_extractors/cargo.rs +++ b/src/version_extractors/cargo.rs @@ -30,7 +30,7 @@ struct CargoPackage { impl VersionExtractor for CargoVersion { type ConfigType = CargoVersionConfig; - async fn extract(&self, config: Self::ConfigType) -> Result<String> { + async fn get_last_version(&self, config: Self::ConfigType) -> Result<String> { let path = config .manifest_path .unwrap_or_else(|| PathBuf::from("Cargo.toml")); -
modified src/version_extractors/git.rs
diff --git a/src/version_extractors/git.rs b/src/version_extractors/git.rs index 75a148b..adb8a64 100644 --- a/src/version_extractors/git.rs +++ b/src/version_extractors/git.rs @@ -36,7 +36,7 @@ pub struct GitVersion; impl VersionExtractor for GitVersion { type ConfigType = GitVersionConfig; - async fn extract(&self, config: Self::ConfigType) -> Result<String> { + async fn get_last_version(&self, config: Self::ConfigType) -> Result<String> { let dirty_arg = format!("--dirty={}", config.dirty_suffix); let output = tokio::process::Command::new("git") @@ -60,4 +60,30 @@ impl VersionExtractor for GitVersion { Ok(version.to_owned()) } + + async fn get_all_versions(&self, config: Self::ConfigType) -> Result<Vec<String>> { + let output = tokio::process::Command::new("git") + .args(["tag", "--list", "--sort=version:refname"]) + .output() + .await + .into_diagnostic()?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(miette!("git tag failed:\n{stderr}")); + } + + let raw = String::from_utf8(output.stdout).into_diagnostic()?; + + let versions = raw + .lines() + .filter(|s| !s.is_empty()) + .map(|tag| match &config.tag_prefix { + Some(prefix) => tag.strip_prefix(prefix.as_str()).unwrap_or(tag).to_owned(), + None => tag.to_owned(), + }) + .collect(); + + Ok(versions) + } } -
modified src/version_extractors/mod.rs
diff --git a/src/version_extractors/mod.rs b/src/version_extractors/mod.rs index 5f621a0..d1473ba 100644 --- a/src/version_extractors/mod.rs +++ b/src/version_extractors/mod.rs @@ -1,4 +1,4 @@ -use miette::Result; +use miette::{Result, bail}; use serde::Deserialize; pub mod cargo; @@ -11,7 +11,11 @@ use git::{GitVersion, GitVersionConfig}; pub trait VersionExtractor { type ConfigType: Default + for<'de> Deserialize<'de> + Clone; - async fn extract(&self, config: Self::ConfigType) -> Result<String>; + async fn get_last_version(&self, config: Self::ConfigType) -> Result<String>; + + async fn get_all_versions(&self, _config: Self::ConfigType) -> Result<Vec<String>> { + bail!("get_all_versions is not supported by this version extractor") + } } #[derive(Debug, Deserialize)] @@ -44,8 +48,15 @@ pub enum AnyVersionExtractor { impl AnyVersionExtractor { pub async fn extract(&self) -> Result<String> { match self { - Self::Cargo(config) => CargoVersion.extract(config.clone()).await, - Self::Git(config) => GitVersion.extract(config.clone()).await, + Self::Cargo(config) => CargoVersion.get_last_version(config.clone()).await, + Self::Git(config) => GitVersion.get_last_version(config.clone()).await, + } + } + + pub async fn extract_all(&self) -> Result<Vec<String>> { + match self { + Self::Cargo(config) => CargoVersion.get_all_versions(config.clone()).await, + Self::Git(config) => GitVersion.get_all_versions(config.clone()).await, } } }