aboutsummaryrefslogtreecommitdiff
path: root/src/release_notes.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/release_notes.rs')
-rw-r--r--src/release_notes.rs125
1 files changed, 114 insertions, 11 deletions
diff --git a/src/release_notes.rs b/src/release_notes.rs
index fcee9a41..7d83dd15 100644
--- a/src/release_notes.rs
+++ b/src/release_notes.rs
@@ -16,6 +16,12 @@ pub struct ReleaseNotes {
struct StoredReleaseNotes {
version: String,
body: String,
+ #[serde(default = "default_show_on_startup")]
+ show_on_startup: bool,
+}
+
+fn default_show_on_startup() -> bool {
+ true
}
pub fn pending_path() -> PathBuf {
@@ -34,15 +40,22 @@ fn save_pending_to_path(path: &Path, version: &str, body: &str) -> std::io::Resu
return clear_pending_at(path);
}
+ write_stored_to_path(
+ path,
+ &StoredReleaseNotes {
+ version: version.to_string(),
+ body,
+ show_on_startup: true,
+ },
+ )
+}
+
+fn write_stored_to_path(path: &Path, stored: &StoredReleaseNotes) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
- let stored = StoredReleaseNotes {
- version: version.to_string(),
- body,
- };
- let json = serde_json::to_string_pretty(&stored).map_err(std::io::Error::other)?;
+ let json = serde_json::to_string_pretty(stored).map_err(std::io::Error::other)?;
let tmp_path = path.with_extension(format!("json.tmp.{}", std::process::id()));
fs::write(&tmp_path, json)?;
if let Err(err) = fs::rename(&tmp_path, path) {
@@ -52,31 +65,61 @@ fn save_pending_to_path(path: &Path, version: &str, body: &str) -> std::io::Resu
Ok(())
}
+fn load_stored_from_path(path: &Path) -> Option<StoredReleaseNotes> {
+ let content = fs::read_to_string(path).ok()?;
+ serde_json::from_str(&content).ok()
+}
+
pub fn load_pending_for_current_version() -> Option<ReleaseNotes> {
load_pending_from_path(&pending_path(), env!("CARGO_PKG_VERSION"))
}
+pub fn load_latest() -> Option<ReleaseNotes> {
+ load_latest_from_path(&pending_path(), env!("CARGO_PKG_VERSION"))
+}
+
fn load_pending_from_path(path: &Path, current_version: &str) -> Option<ReleaseNotes> {
- let content = fs::read_to_string(path).ok()?;
- let stored: StoredReleaseNotes = serde_json::from_str(&content).ok()?;
- if stored.version != current_version {
+ let stored = load_stored_from_path(path)?;
+ if stored.version != current_version || !stored.show_on_startup {
return None;
}
+ release_notes_from_stored(stored, current_version)
+}
+fn load_latest_from_path(path: &Path, current_version: &str) -> Option<ReleaseNotes> {
+ let stored = load_stored_from_path(path)?;
+ release_notes_from_stored(stored, current_version)
+}
+
+fn release_notes_from_stored(
+ stored: StoredReleaseNotes,
+ current_version: &str,
+) -> Option<ReleaseNotes> {
let body = normalize_body(&stored.body);
if body.is_empty() {
return None;
}
Some(ReleaseNotes {
+ preview: stored.version != current_version,
version: stored.version,
body,
- preview: false,
})
}
-pub fn clear_pending() -> std::io::Result<()> {
- clear_pending_at(&pending_path())
+pub fn mark_current_version_seen() -> std::io::Result<()> {
+ mark_current_version_seen_at(&pending_path(), env!("CARGO_PKG_VERSION"))
+}
+
+fn mark_current_version_seen_at(path: &Path, current_version: &str) -> std::io::Result<()> {
+ let Some(mut stored) = load_stored_from_path(path) else {
+ return Ok(());
+ };
+ if stored.version != current_version || !stored.show_on_startup {
+ return Ok(());
+ }
+ stored.show_on_startup = false;
+ write_stored_to_path(path, &stored)
}
fn clear_pending_at(path: &Path) -> std::io::Result<()> {
@@ -151,4 +194,64 @@ mod tests {
"### Changed\n- One\n\n### Fixed\n- Two"
);
}
+
+ #[test]
+ fn load_latest_keeps_future_version_previewable_before_restart() {
+ let path = std::env::temp_dir().join(format!(
+ "herdr-release-notes-{}-{}.json",
+ std::process::id(),
+ "preview"
+ ));
+ let _ = clear_pending_at(&path);
+ save_pending_to_path(&path, "0.3.2", "### Changed\n- One").unwrap();
+
+ let notes = load_latest_from_path(&path, "0.3.1").expect("latest notes");
+ assert_eq!(notes.version, "0.3.2");
+ assert_eq!(notes.body, "### Changed\n- One");
+ assert!(notes.preview);
+
+ clear_pending_at(&path).unwrap();
+ }
+
+ #[test]
+ fn marking_current_version_seen_preserves_latest_notes_without_startup_modal() {
+ let path = std::env::temp_dir().join(format!(
+ "herdr-release-notes-{}-{}.json",
+ std::process::id(),
+ "seen"
+ ));
+ let _ = clear_pending_at(&path);
+ save_pending_to_path(&path, "0.3.1", "### Changed\n- One").unwrap();
+
+ let startup = load_pending_from_path(&path, "0.3.1").expect("startup notes");
+ assert!(!startup.preview);
+
+ mark_current_version_seen_at(&path, "0.3.1").unwrap();
+
+ assert!(load_pending_from_path(&path, "0.3.1").is_none());
+ let latest = load_latest_from_path(&path, "0.3.1").expect("latest notes");
+ assert_eq!(latest.version, "0.3.1");
+ assert!(!latest.preview);
+
+ clear_pending_at(&path).unwrap();
+ }
+
+ #[test]
+ fn legacy_notes_without_show_on_startup_default_to_unseen() {
+ let path = std::env::temp_dir().join(format!(
+ "herdr-release-notes-{}-{}.json",
+ std::process::id(),
+ "legacy"
+ ));
+ let _ = clear_pending_at(&path);
+ fs::write(
+ &path,
+ "{\n \"version\": \"0.3.1\",\n \"body\": \"### Changed\\n- One\"\n}",
+ )
+ .unwrap();
+
+ assert!(load_pending_from_path(&path, "0.3.1").is_some());
+
+ clear_pending_at(&path).unwrap();
+ }
}