Compare commits

...

3 commits

Author SHA1 Message Date
2ab2b6a52b
ci: bump to 0.2.2
All checks were successful
Continuous Integration / Lint, Check & Test (push) Successful in 2m7s
Continuous Integration / Build Package (push) Successful in 2m45s
Release / Build and Release (push) Successful in 2m56s
2026-04-07 09:57:30 +02:00
22ada87de6
feat(timesheet): make hour-output default, decimal with two decimals 2026-04-07 09:56:46 +02:00
7f4d3bb147
ci: remove cargo-audito from pre-commit-hooks 2026-04-07 09:42:38 +02:00
6 changed files with 71 additions and 60 deletions

2
Cargo.lock generated
View file

@ -787,7 +787,7 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]] [[package]]
name = "streamd" name = "streamd"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz", "chrono-tz",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "streamd" name = "streamd"
version = "0.2.1" version = "0.2.2"
edition = "2021" edition = "2021"
description = "Personal knowledge management and time-tracking CLI using @Tag annotations" description = "Personal knowledge management and time-tracking CLI using @Tag annotations"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"

View file

@ -103,12 +103,6 @@
package = toolchain; package = toolchain;
}; };
commitizen.enable = true; commitizen.enable = true;
cargo-audit = {
enable = true;
entry = "${pkgs.cargo-audit}/bin/cargo-audit audit";
files = "^Cargo\\.(toml|lock)$";
pass_filenames = false;
};
}; };
}; };

View file

@ -51,9 +51,9 @@ pub enum Commands {
/// Display extracted timesheets /// Display extracted timesheets
Timesheet { Timesheet {
/// Display time as minutes (HH:MM) instead of decimal hours (H.Hh) /// Display time as decimal hours (X.XXh) instead of the default HH:MM format
#[arg(short, long)] #[arg(short, long)]
minutes: bool, decimal: bool,
/// Show all timecards grouped by day instead of the summary report /// Show all timecards grouped by day instead of the summary report
#[arg(short, long)] #[arg(short, long)]

View file

@ -42,28 +42,39 @@ fn load_all_shards(base_folder: &Path) -> Result<Vec<LocalizedShard>, StreamdErr
Ok(shards) Ok(shards)
} }
enum DisplayMode {
Minutes,
Decimal,
}
/// Format minutes with sign for display. /// Format minutes with sign for display.
fn format_diff(minutes: i64, use_minutes: bool) -> String { fn format_diff(minutes: i64, mode: &DisplayMode) -> String {
let sign = if minutes >= 0 { "+" } else { "-" }; let sign = if minutes >= 0 { "+" } else { "-" };
if use_minutes { match mode {
DisplayMode::Minutes => {
let h = minutes.unsigned_abs() / 60; let h = minutes.unsigned_abs() / 60;
let m = minutes.unsigned_abs() % 60; let m = minutes.unsigned_abs() % 60;
format!("{}{}:{:02}", sign, h, m) format!("{}{}:{:02}", sign, h, m)
} else { }
DisplayMode::Decimal => {
let hours = minutes.unsigned_abs() as f64 / 60.0; let hours = minutes.unsigned_abs() as f64 / 60.0;
format!("{}{:.1}h", sign, hours) format!("{}{:.2}h", sign, hours)
}
} }
} }
/// Format minutes for display without sign. /// Format minutes for display without sign.
fn format_hours(minutes: i64, use_minutes: bool) -> String { fn format_hours(minutes: i64, mode: &DisplayMode) -> String {
if use_minutes { match mode {
DisplayMode::Minutes => {
let h = minutes.unsigned_abs() / 60; let h = minutes.unsigned_abs() / 60;
let m = minutes.unsigned_abs() % 60; let m = minutes.unsigned_abs() % 60;
format!("{}:{:02}", h, m) format!("{}:{:02}", h, m)
} else { }
DisplayMode::Decimal => {
let hours = minutes.unsigned_abs() as f64 / 60.0; let hours = minutes.unsigned_abs() as f64 / 60.0;
format!("{:.1}h", hours) format!("{:.2}h", hours)
}
} }
} }
@ -90,8 +101,8 @@ fn print_header() {
} }
/// Print a month report. /// Print a month report.
fn print_month(month: &MonthReport, use_minutes: bool) { fn print_month(month: &MonthReport, mode: &DisplayMode) {
let diff_str = format_diff(month.diff(), use_minutes); let diff_str = format_diff(month.diff(), mode);
let month_title = format!("{} {}", month.month_name(), month.year); let month_title = format!("{} {}", month.month_name(), month.year);
// Month header with diff // Month header with diff
@ -112,9 +123,9 @@ fn print_month(month: &MonthReport, use_minutes: bool) {
for day in &month.days { for day in &month.days {
let date_str = day.date.format("%Y-%m-%d").to_string(); let date_str = day.date.format("%Y-%m-%d").to_string();
let weekday = weekday_abbrev(day.date); let weekday = weekday_abbrev(day.date);
let expected = format_hours(day.expected_minutes, use_minutes); let expected = format_hours(day.expected_minutes, mode);
let actual = format_hours(day.actual_minutes, use_minutes); let actual = format_hours(day.actual_minutes, mode);
let diff = format_diff(day.diff(), use_minutes); let diff = format_diff(day.diff(), mode);
let type_str = match day.day_type { let type_str = match day.day_type {
DayType::Regular => String::new(), DayType::Regular => String::new(),
@ -147,26 +158,26 @@ fn print_month(month: &MonthReport, use_minutes: bool) {
println!(" {}", light_line); println!(" {}", light_line);
println!( println!(
" Monthly: {:>7} {:>7} {:>6}", " Monthly: {:>7} {:>7} {:>6}",
format_hours(month.total_expected(), use_minutes), format_hours(month.total_expected(), mode),
format_hours(month.total_actual(), use_minutes), format_hours(month.total_actual(), mode),
format_diff(month.diff(), use_minutes) format_diff(month.diff(), mode)
); );
println!(); println!();
} }
/// Print the cumulative balance. /// Print the cumulative balance.
fn print_cumulative_balance(balance: i64, use_minutes: bool) { fn print_cumulative_balance(balance: i64, mode: &DisplayMode) {
let light_line = "\u{2500}".repeat(SEPARATOR_WIDTH); let light_line = "\u{2500}".repeat(SEPARATOR_WIDTH);
println!("{}", light_line); println!("{}", light_line);
println!( println!(
" CUMULATIVE BALANCE: {}", " CUMULATIVE BALANCE: {}",
format_diff(balance, use_minutes) format_diff(balance, mode)
); );
println!("{}", light_line); println!("{}", light_line);
} }
/// Print warnings section. /// Print warnings section.
fn print_warnings(report: &TimesheetReport, use_minutes: bool) { fn print_warnings(report: &TimesheetReport, mode: &DisplayMode) {
if !report.has_warnings() { if !report.has_warnings() {
return; return;
} }
@ -231,7 +242,7 @@ fn print_warnings(report: &TimesheetReport, use_minutes: bool) {
println!( println!(
" - {}: {} worked (no period configured)", " - {}: {} worked (no period configured)",
w.date.format("%Y-%m-%d"), w.date.format("%Y-%m-%d"),
format_hours(*minutes_worked, use_minutes) format_hours(*minutes_worked, mode)
); );
} }
} }
@ -297,7 +308,12 @@ fn print_debug(report: &TimesheetReport, timesheets: &[Timesheet]) {
} }
} }
pub fn run(use_minutes: bool, debug: bool) -> Result<(), StreamdError> { pub fn run(decimal: bool, debug: bool) -> Result<(), StreamdError> {
let mode = if decimal {
DisplayMode::Decimal
} else {
DisplayMode::Minutes
};
let settings = Settings::load()?; let settings = Settings::load()?;
let base_folder = Path::new(&settings.base_folder); let base_folder = Path::new(&settings.base_folder);
@ -339,11 +355,11 @@ pub fn run(use_minutes: bool, debug: bool) -> Result<(), StreamdError> {
print_header(); print_header();
for month in &report.months { for month in &report.months {
print_month(month, use_minutes); print_month(month, &mode);
} }
print_cumulative_balance(report.cumulative_balance, use_minutes); print_cumulative_balance(report.cumulative_balance, &mode);
print_warnings(&report, use_minutes); print_warnings(&report, &mode);
} }
Ok(()) Ok(())
@ -355,33 +371,34 @@ mod tests {
#[test] #[test]
fn test_format_hours_decimal() { fn test_format_hours_decimal() {
assert_eq!(format_hours(480, false), "8.0h"); assert_eq!(format_hours(480, &DisplayMode::Decimal), "8.00h");
assert_eq!(format_hours(510, false), "8.5h"); assert_eq!(format_hours(510, &DisplayMode::Decimal), "8.50h");
assert_eq!(format_hours(0, false), "0.0h"); assert_eq!(format_hours(507, &DisplayMode::Decimal), "8.45h");
assert_eq!(format_hours(0, &DisplayMode::Decimal), "0.00h");
} }
#[test] #[test]
fn test_format_hours_minutes() { fn test_format_hours_minutes() {
assert_eq!(format_hours(480, true), "8:00"); assert_eq!(format_hours(480, &DisplayMode::Minutes), "8:00");
assert_eq!(format_hours(510, true), "8:30"); assert_eq!(format_hours(510, &DisplayMode::Minutes), "8:30");
assert_eq!(format_hours(0, true), "0:00"); assert_eq!(format_hours(0, &DisplayMode::Minutes), "0:00");
assert_eq!(format_hours(75, true), "1:15"); assert_eq!(format_hours(75, &DisplayMode::Minutes), "1:15");
assert_eq!(format_hours(77, true), "1:17"); assert_eq!(format_hours(77, &DisplayMode::Minutes), "1:17");
assert_eq!(format_hours(200, true), "3:20"); assert_eq!(format_hours(200, &DisplayMode::Minutes), "3:20");
} }
#[test] #[test]
fn test_format_diff_decimal() { fn test_format_diff_decimal() {
assert_eq!(format_diff(30, false), "+0.5h"); assert_eq!(format_diff(30, &DisplayMode::Decimal), "+0.50h");
assert_eq!(format_diff(-90, false), "-1.5h"); assert_eq!(format_diff(-90, &DisplayMode::Decimal), "-1.50h");
assert_eq!(format_diff(0, false), "+0.0h"); assert_eq!(format_diff(0, &DisplayMode::Decimal), "+0.00h");
} }
#[test] #[test]
fn test_format_diff_minutes() { fn test_format_diff_minutes() {
assert_eq!(format_diff(30, true), "+0:30"); assert_eq!(format_diff(30, &DisplayMode::Minutes), "+0:30");
assert_eq!(format_diff(-90, true), "-1:30"); assert_eq!(format_diff(-90, &DisplayMode::Minutes), "-1:30");
assert_eq!(format_diff(0, true), "+0:00"); assert_eq!(format_diff(0, &DisplayMode::Minutes), "+0:00");
assert_eq!(format_diff(75, true), "+1:15"); assert_eq!(format_diff(75, &DisplayMode::Minutes), "+1:15");
} }
} }

View file

@ -15,8 +15,8 @@ fn main() -> miette::Result<()> {
Some(TodoAction::Done { number }) => streamd::cli::commands::todo::run_done(number)?, Some(TodoAction::Done { number }) => streamd::cli::commands::todo::run_done(number)?,
}, },
Some(Commands::Edit { number }) => streamd::cli::commands::edit::run(number)?, Some(Commands::Edit { number }) => streamd::cli::commands::edit::run(number)?,
Some(Commands::Timesheet { minutes, debug }) => { Some(Commands::Timesheet { decimal, debug }) => {
streamd::cli::commands::timesheet::run(minutes, debug)? streamd::cli::commands::timesheet::run(decimal, debug)?
} }
Some(Commands::Completions { shell }) => { Some(Commands::Completions { shell }) => {
streamd::cli::commands::completions::run(shell); streamd::cli::commands::completions::run(shell);