1mod document;
12mod metadata;
13
14pub use document::{
15 PackageMetadata, Release, ensure_releases_section, recent_releases, update_document,
16 update_document_with_releases, update_releases_section,
17};
18
19mod readme_support {
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 trait TemporaryFile: Write {
28 fn set_permissions(&self, permissions: fs::Permissions) -> std::io::Result<()>;
29 fn sync_all(&self) -> std::io::Result<()>;
30 }
31
32 impl TemporaryFile for fs::File {
33 fn set_permissions(&self, permissions: fs::Permissions) -> std::io::Result<()> {
34 fs::File::set_permissions(self, permissions)
35 }
36
37 fn sync_all(&self) -> std::io::Result<()> {
38 fs::File::sync_all(self)
39 }
40 }
41
42 fn prepare_temporary_file(
43 file: &mut impl TemporaryFile,
44 path: &std::path::Path,
45 document: &str,
46 ) -> Result<()> {
47 file.write_all(document.as_bytes())?;
48 file.set_permissions(fs::metadata(path)?.permissions())?;
49 file.sync_all()?;
50 Ok(())
51 }
52
53 fn write_document_atomically(path: &std::path::Path, document: &str) -> Result<()> {
54 let directory = path
55 .parent()
56 .expect("a canonical readme file path should have a parent directory");
57 let mut temporary = tempfile::NamedTempFile::new_in(directory)?;
58 prepare_temporary_file(temporary.as_file_mut(), path, document)?;
59 temporary
60 .persist(path)
61 .map_err(|error| Error::from(error.error))?;
62 Ok(())
63 }
64
65 pub(super) fn update(context: &mut Context, path: PathBuf) -> Result<()> {
67 let path = context.root().join(path).canonicalize()?;
68 let document = fs::read_to_string(&path)
69 .map_err(|error| Error::new(format!("{}: {error}", path.display())))?;
70 let package = read_project_package(context)?;
71 let release_path = context.root().join("releases.md");
72 let releases = if release_path.is_file() {
73 let release_document = fs::read_to_string(&release_path)
74 .map_err(|error| Error::new(format!("{}: {error}", release_path.display())))?;
75 recent_releases(&release_document)
76 } else {
77 Vec::new()
78 };
79 let updated = update_document_with_releases(&document, package.as_ref(), &releases);
80
81 if updated == document {
82 return Ok(());
83 }
84
85 write_document_atomically(&path, &updated)
86 }
87
88 #[cfg(test)]
89 #[path = "readme_tests.rs"]
90 mod tests;
91}
92
93#[bake::task]
95pub fn update(
96 context: &mut bake::Context,
97 #[bake(
98 default = "readme.md",
99 help = "readme file relative to the project root."
100 )]
101 path: std::path::PathBuf,
102) -> bake::Result<()> {
103 readme_support::update(context, path)
104}