Compare commits

..

No commits in common. "a9acd34801bc782450f53c2febe05b844b0405fd" and "e4ed1d839ea315eb38fe01e251fc830c9cc5f1b0" have entirely different histories.

6 changed files with 27 additions and 81 deletions

View file

@ -2,8 +2,6 @@ name: Release
on: on:
push: push:
branches:
- main
workflow_dispatch: workflow_dispatch:
jobs: jobs:

6
Cargo.lock generated
View file

@ -285,9 +285,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.4.1" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f"
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
@ -787,7 +787,7 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]] [[package]]
name = "streamd" name = "streamd"
version = "0.2.0" version = "0.1.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz", "chrono-tz",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "streamd" name = "streamd"
version = "0.2.0" version = "0.1.1"
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

@ -50,11 +50,7 @@ pub enum Commands {
}, },
/// Display extracted timesheets /// Display extracted timesheets
Timesheet { Timesheet,
/// Display time as minutes (HH:MM) instead of decimal hours (H.Hh)
#[arg(short, long)]
minutes: bool,
},
/// Generate shell completions /// Generate shell completions
Completions { Completions {

View file

@ -41,14 +41,8 @@ fn load_all_shards(base_folder: &Path) -> Result<Vec<LocalizedShard>, StreamdErr
} }
/// Format hours with sign for display. /// Format hours with sign for display.
fn format_diff(hours: f64, use_minutes: bool) -> String { fn format_diff(hours: f64) -> String {
if use_minutes { if hours >= 0.0 {
let total_minutes = (hours * 60.0).round() as i32;
let h = total_minutes.abs() / 60;
let m = total_minutes.abs() % 60;
let sign = if hours >= 0.0 { "+" } else { "-" };
format!("{}{}:{:02}", sign, h, m)
} else if hours >= 0.0 {
format!("+{:.1}h", hours.abs()) format!("+{:.1}h", hours.abs())
} else { } else {
format!("{:.1}h", hours) format!("{:.1}h", hours)
@ -56,15 +50,8 @@ fn format_diff(hours: f64, use_minutes: bool) -> String {
} }
/// Format hours for display without sign. /// Format hours for display without sign.
fn format_hours(hours: f64, use_minutes: bool) -> String { fn format_hours(hours: f64) -> String {
if use_minutes { format!("{:.1}h", hours.abs())
let total_minutes = (hours * 60.0).round() as i32;
let h = total_minutes.abs() / 60;
let m = total_minutes.abs() % 60;
format!("{}:{:02}", h, m)
} else {
format!("{:.1}h", hours.abs())
}
} }
/// Get the weekday abbreviation. /// Get the weekday abbreviation.
@ -90,8 +77,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) {
let diff_str = format_diff(month.diff(), use_minutes); let diff_str = format_diff(month.diff());
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 +99,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_hours, use_minutes); let expected = format_hours(day.expected_hours);
let actual = format_hours(day.actual_hours, use_minutes); let actual = format_hours(day.actual_hours);
let diff = format_diff(day.diff(), use_minutes); let diff = format_diff(day.diff());
let type_str = match day.day_type { let type_str = match day.day_type {
DayType::Regular => String::new(), DayType::Regular => String::new(),
@ -147,26 +134,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()),
format_hours(month.total_actual(), use_minutes), format_hours(month.total_actual()),
format_diff(month.diff(), use_minutes) format_diff(month.diff())
); );
println!(); println!();
} }
/// Print the cumulative balance. /// Print the cumulative balance.
fn print_cumulative_balance(balance: f64, use_minutes: bool) { fn print_cumulative_balance(balance: f64) {
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)
); );
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) {
if !report.has_warnings() { if !report.has_warnings() {
return; return;
} }
@ -229,9 +216,9 @@ fn print_warnings(report: &TimesheetReport, use_minutes: bool) {
for w in &outside_period_warnings { for w in &outside_period_warnings {
if let DayWarning::OutsidePeriod { hours_worked } = &w.warning { if let DayWarning::OutsidePeriod { hours_worked } = &w.warning {
println!( println!(
" - {}: {} worked (no period configured)", " - {}: {:.1}h worked (no period configured)",
w.date.format("%Y-%m-%d"), w.date.format("%Y-%m-%d"),
format_hours(*hours_worked, use_minutes) hours_worked
); );
} }
} }
@ -239,7 +226,7 @@ fn print_warnings(report: &TimesheetReport, use_minutes: bool) {
} }
} }
pub fn run(use_minutes: bool) -> Result<(), StreamdError> { pub fn run() -> Result<(), StreamdError> {
let settings = Settings::load()?; let settings = Settings::load()?;
let base_folder = Path::new(&settings.base_folder); let base_folder = Path::new(&settings.base_folder);
@ -279,46 +266,11 @@ pub fn run(use_minutes: 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);
} }
print_cumulative_balance(report.cumulative_balance, use_minutes); print_cumulative_balance(report.cumulative_balance);
print_warnings(&report, use_minutes); print_warnings(&report);
Ok(()) Ok(())
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_hours_decimal() {
assert_eq!(format_hours(8.0, false), "8.0h");
assert_eq!(format_hours(8.5, false), "8.5h");
assert_eq!(format_hours(0.0, false), "0.0h");
}
#[test]
fn test_format_hours_minutes() {
assert_eq!(format_hours(8.0, true), "8:00");
assert_eq!(format_hours(8.5, true), "8:30");
assert_eq!(format_hours(0.0, true), "0:00");
assert_eq!(format_hours(1.25, true), "1:15");
}
#[test]
fn test_format_diff_decimal() {
assert_eq!(format_diff(0.5, false), "+0.5h");
assert_eq!(format_diff(-1.5, false), "-1.5h");
assert_eq!(format_diff(0.0, false), "+0.0h");
}
#[test]
fn test_format_diff_minutes() {
assert_eq!(format_diff(0.5, true), "+0:30");
assert_eq!(format_diff(-1.5, true), "-1:30");
assert_eq!(format_diff(0.0, true), "+0:00");
assert_eq!(format_diff(1.25, true), "+1:15");
}
}

View file

@ -15,7 +15,7 @@ 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 }) => streamd::cli::commands::timesheet::run(minutes)?, Some(Commands::Timesheet) => streamd::cli::commands::timesheet::run()?,
Some(Commands::Completions { shell }) => { Some(Commands::Completions { shell }) => {
streamd::cli::commands::completions::run(shell); streamd::cli::commands::completions::run(shell);
} }