Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,11 @@ pub fn main_loop(config: &Config) -> anyhow::Result<(Sender<()>, Option<JoinHand
let all_summaries = merge_summaries_on_same_date(all_summaries);

// WRITE
let Writers {
plan_writers,
summary_writers,
global_summary_writers,
} = get_writers(start_time, config);
let mut writers = get_writers(start_time, config);

write_plan(&all_life_lapses, plan_writers)?;
write_summary(&all_summaries, summary_writers)?;
write_global_summary(&all_summaries, global_summary_writers)?;
write_plan(&all_life_lapses, &mut writers.plan_writers)?;
write_summary(&all_summaries, &mut writers.summary_writers)?;
write_global_summary(&all_summaries, &mut writers.global_summary_writers)?;
// END WRITE

// WATCHING
Expand Down
12 changes: 6 additions & 6 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ pub struct Writers {
pub global_summary_writers: Vec<Writer>,
}

pub fn write_plan(all_life_lapses: &[LifeLapse], mut plan_writers: Vec<Writer>) -> Result<()> {
for plan_writer in &mut plan_writers {
pub fn write_plan(all_life_lapses: &[LifeLapse], plan_writers: &mut [Writer]) -> Result<()> {
for plan_writer in plan_writers {
write_to(|| format_lifelapses(all_life_lapses), plan_writer)?;
}
Ok(())
}

pub fn write_summary(
all_summaries: &[(Timestamp, Summary)],
mut summary_writers: Vec<Writer>,
summary_writers: &mut [Writer],
) -> Result<()> {
for (ts, summary) in all_summaries {
for summary_writer in &mut summary_writers {
for summary_writer in &mut *summary_writers {
write_to(
|| format_category_summary(compute_context_summary(summary), ts.date()),
summary_writer,
Expand All @@ -46,11 +46,11 @@ pub fn write_summary(

pub fn write_global_summary(
all_summaries: &[(Timestamp, Summary)],
mut summary_writers: Vec<Writer>,
summary_writers: &mut [Writer],
) -> Result<()> {
let only_summaries: Vec<Summary> = all_summaries.iter().map(|(_, s)| s.clone()).collect();
let summary: Summary = merge_all_summaries(&only_summaries);
for summary_writer in &mut summary_writers {
for summary_writer in summary_writers {
let date = Local::now().date();
write_to(
|| {
Expand Down
75 changes: 75 additions & 0 deletions src/pretty_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ mod tests {
let mut file = File::create(&file_path).unwrap();
writeln!(file, "line 1").unwrap();
writeln!(file, "line 2").unwrap();
assert!(file_path.exists(), "File should exist before cleanup");

// New content is a superset
let new_content = "line 1\nline 2\nline 3\n";
Expand All @@ -265,6 +266,7 @@ mod tests {
let mut file = File::create(&file_path).unwrap();
writeln!(file, "line 1").unwrap();
writeln!(file, "line 2").unwrap();
assert!(file_path.exists(), "File should exist before cleanup");

// New content is different
let new_content = "line 1\nline 3\n";
Expand All @@ -284,6 +286,7 @@ mod tests {
let file_path = dir_path.join(format!("{}_old.txt", prefix));
let mut file = File::create(&file_path).unwrap();
writeln!(file, "line 1").unwrap();
assert!(file_path.exists(), "File should exist before cleanup");

// New content is exact match
let new_content = "line 1\n";
Expand All @@ -306,6 +309,7 @@ mod tests {
let file_path = dir_path.join("other_prefix_old.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "line 1").unwrap();
assert!(file_path.exists(), "File should exist before cleanup");

// New content is superset
let new_content = "line 1\nline 2\n";
Expand All @@ -317,4 +321,75 @@ mod tests {
"File with different prefix should be preserved"
);
}

#[test]
fn test_cleanup_redundant_files_multiple_redundant() {
let dir = tempdir().unwrap();
let dir_path = dir.path();
let prefix = "test_prefix";

// Create multiple existing files
let file_path1 = dir_path.join(format!("{}_old1.txt", prefix));
let file_path2 = dir_path.join(format!("{}_old2.txt", prefix));
let mut file1 = File::create(&file_path1).unwrap();
let mut file2 = File::create(&file_path2).unwrap();
writeln!(file1, "line 1").unwrap();
writeln!(file2, "line 1\nline 2").unwrap();
assert!(file_path1.exists(), "File 1 should exist before cleanup");
assert!(file_path2.exists(), "File 2 should exist before cleanup");

// New content is a superset of both
let new_content = "line 1\nline 2\nline 3\n";

cleanup_redundant_files(dir_path, prefix, new_content).unwrap();

assert!(!file_path1.exists(), "Old file 1 should be deleted");
assert!(!file_path2.exists(), "Old file 2 should be deleted");
}

#[test]
fn test_cleanup_redundant_files_not_a_prefix_on_line_boundary() {
let dir = tempdir().unwrap();
let dir_path = dir.path();
let prefix = "test_prefix";

// Create an existing file
let file_path = dir_path.join(format!("{}_old.txt", prefix));
let mut file = File::create(&file_path).unwrap();
write!(file, "line 1 partial").unwrap();
assert!(file_path.exists(), "File should exist before cleanup");

// New content starts with the old one, but not on a line boundary
let new_content = "line 1 partial extra";

cleanup_redundant_files(dir_path, prefix, new_content).unwrap();

assert!(
file_path.exists(),
"Old file should be preserved (not a prefix on line boundary)"
);
}

#[test]
fn test_cleanup_redundant_files_new_content_shorter() {
let dir = tempdir().unwrap();
let dir_path = dir.path();
let prefix = "test_prefix";

// Create an existing file
let file_path = dir_path.join(format!("{}_old.txt", prefix));
let mut file = File::create(&file_path).unwrap();
writeln!(file, "line 1\nline 2").unwrap();
assert!(file_path.exists(), "File should exist before cleanup");

// New content is shorter
let new_content = "line 1\n";

cleanup_redundant_files(dir_path, prefix, new_content).unwrap();

assert!(
file_path.exists(),
"Old file should be preserved (new content is shorter)"
);
}
}
138 changes: 138 additions & 0 deletions tests/cleanup_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,141 @@ fn cleanup_preserves_non_redundant_files() {
"Should have two plan files (history preserved)"
);
}

#[test]
fn cleanup_removes_redundant_summary_files() {
let dir = tempdir().unwrap();
let activities_path = dir.path().join("activities.txt");
let config_path = dir.path().join("config.toml");
let output_dir = dir.path().join("history");

fs::create_dir(&output_dir).unwrap();

// Initial activities
fs::write(&activities_path, "2025-01-01 10:00\n1 0 Task 1 @work\n").unwrap();

fs::write(
&config_path,
dedent(&format!(
r#"
activity_paths = ["{}"]
[quadrants]
Q1 = ["@work"]
"#,
activities_path.to_str().unwrap()
)),
)
.unwrap();

let mut cmd = assert_cmd::Command::from_std(Command::new(env!("CARGO_BIN_EXE_tiro")));
cmd.arg("--config")
.arg(config_path.to_str().unwrap())
.arg("--summary")
.arg(output_dir.to_str().unwrap())
.arg("--quiet");

cmd.assert().success();

// Find generated file recursively
// Note: The file structure is history/YYYY-wWW/YYYY-MM-DD/filename.txt
let find_files = || {
let mut files = vec![];
for entry in walkdir::WalkDir::new(&output_dir) {
let entry = entry.unwrap();
if entry.file_type().is_file() && entry.path().extension().is_some_and(|e| e == "txt") {
files.push(entry.path().to_owned());
}
}
files
};

let files_1 = find_files();
assert_eq!(
files_1.len(),
2,
"Should have created one summary file and one global summary file"
);
let summary_file_1 = files_1
.iter()
.find(|f| {
f.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("summary")
})
.unwrap()
.clone();
let global_summary_file_1 = files_1
.iter()
.find(|f| {
f.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("global_summary")
})
.unwrap()
.clone();

// Run again with an additional activity with a new category.
// This should result in a summary that is a superset of the previous one.
fs::write(
&activities_path,
"2025-01-01 10:00\n1 0 Task 1 @work\n1 0 Task 2 @zen\n",
)
.unwrap();

let mut cmd = assert_cmd::Command::from_std(Command::new(env!("CARGO_BIN_EXE_tiro")));
cmd.arg("--config")
.arg(config_path.to_str().unwrap())
.arg("--summary")
.arg(output_dir.to_str().unwrap())
.arg("--quiet");

cmd.assert().success();

let files_2 = find_files();
assert_eq!(
files_2.len(),
2,
"Should still have two summary files (redundant one deleted)"
);

let summary_file_2 = files_2
.iter()
.find(|f| {
f.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("summary")
})
.unwrap()
.clone();
let global_summary_file_2 = files_2
.iter()
.find(|f| {
f.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("global_summary")
})
.unwrap()
.clone();

assert!(
!summary_file_1.exists(),
"Old summary file should be deleted"
);
assert!(
!global_summary_file_1.exists(),
"Old global summary file should be deleted"
);
assert!(summary_file_2.exists(), "New summary file should exist");
assert!(
global_summary_file_2.exists(),
"New global summary file should exist"
);
}