Skip to main content

bake_readme/
lib.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! Reusable `readme.md` maintenance tasks for Bake.
5//!
6//! The task adds a short summary of recent entries from `releases.md` when a
7//! project `readme.md` does not already contain an authored Releases section. When
8//! the project root has a Cargo package manifest, it also adds or refreshes a
9//! generated package entry in the `readme.md` See Also section.
10mod document;
11mod metadata;
12
13pub use document::{
14    PackageMetadata, Release, ensure_releases_section, recent_releases, update_document,
15    update_document_with_releases, update_releases_section,
16};
17
18/// Tasks exported by this package register beneath the `readme` namespace.
19pub mod readme {
20    use bake::{Context, Error, Result};
21    use std::fs;
22    use std::io::Write;
23    use std::path::PathBuf;
24
25    use super::{metadata::read_project_package, recent_releases, update_document_with_releases};
26
27    /// Add recent release notes and Cargo package links to `readme.md` when needed.
28    #[bake::task]
29    pub fn update(
30        context: &mut Context,
31        #[bake(
32            default = "readme.md",
33            help = "readme file relative to the project root."
34        )]
35        path: PathBuf,
36    ) -> Result<()> {
37        let path = context.root().join(path).canonicalize()?;
38        let document = fs::read_to_string(&path)
39            .map_err(|error| Error::new(format!("{}: {error}", path.display())))?;
40        let package = read_project_package(context)?;
41        let release_path = context.root().join("releases.md");
42        let releases = if release_path.is_file() {
43            let release_document = fs::read_to_string(&release_path)
44                .map_err(|error| Error::new(format!("{}: {error}", release_path.display())))?;
45            recent_releases(&release_document)
46        } else {
47            Vec::new()
48        };
49        let updated = update_document_with_releases(&document, package.as_ref(), &releases);
50
51        if updated == document {
52            return Ok(());
53        }
54
55        let directory = path
56            .parent()
57            .ok_or_else(|| Error::new("readme file has no parent directory"))?;
58        let mut temporary = tempfile::NamedTempFile::new_in(directory)?;
59        temporary.write_all(updated.as_bytes())?;
60        temporary
61            .as_file()
62            .set_permissions(fs::metadata(&path)?.permissions())?;
63        temporary.as_file().sync_all()?;
64        temporary
65            .persist(&path)
66            .map_err(|error| Error::from(error.error))?;
67        Ok(())
68    }
69}