Compare commits
No commits in common. "2ab2b6a52b890dec43029f6f70a936a061a17c86" and "60fac418f892f136823b37cdb966df08fb74763c" have entirely different histories.
2ab2b6a52b
...
60fac418f8
6 changed files with 60 additions and 71 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -787,7 +787,7 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "streamd"
|
name = "streamd"
|
||||||
version = "0.2.2"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "streamd"
|
name = "streamd"
|
||||||
version = "0.2.2"
|
version = "0.2.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"
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,12 @@
|
||||||
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;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,9 @@ pub enum Commands {
|
||||||
|
|
||||||
/// Display extracted timesheets
|
/// Display extracted timesheets
|
||||||
Timesheet {
|
Timesheet {
|
||||||
/// Display time as decimal hours (X.XXh) instead of the default HH:MM format
|
/// Display time as minutes (HH:MM) instead of decimal hours (H.Hh)
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
decimal: bool,
|
minutes: 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)]
|
||||||
|
|
|
||||||
|
|
@ -42,39 +42,28 @@ 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, mode: &DisplayMode) -> String {
|
fn format_diff(minutes: i64, use_minutes: bool) -> String {
|
||||||
let sign = if minutes >= 0 { "+" } else { "-" };
|
let sign = if minutes >= 0 { "+" } else { "-" };
|
||||||
match mode {
|
if use_minutes {
|
||||||
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!("{}{:.2}h", sign, hours)
|
format!("{}{:.1}h", sign, hours)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format minutes for display without sign.
|
/// Format minutes for display without sign.
|
||||||
fn format_hours(minutes: i64, mode: &DisplayMode) -> String {
|
fn format_hours(minutes: i64, use_minutes: bool) -> String {
|
||||||
match mode {
|
if use_minutes {
|
||||||
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!("{:.2}h", hours)
|
format!("{:.1}h", hours)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,8 +90,8 @@ fn print_header() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print a month report.
|
/// Print a month report.
|
||||||
fn print_month(month: &MonthReport, mode: &DisplayMode) {
|
fn print_month(month: &MonthReport, use_minutes: bool) {
|
||||||
let diff_str = format_diff(month.diff(), mode);
|
let diff_str = format_diff(month.diff(), use_minutes);
|
||||||
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
|
||||||
|
|
@ -123,9 +112,9 @@ fn print_month(month: &MonthReport, mode: &DisplayMode) {
|
||||||
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, mode);
|
let expected = format_hours(day.expected_minutes, use_minutes);
|
||||||
let actual = format_hours(day.actual_minutes, mode);
|
let actual = format_hours(day.actual_minutes, use_minutes);
|
||||||
let diff = format_diff(day.diff(), mode);
|
let diff = format_diff(day.diff(), use_minutes);
|
||||||
|
|
||||||
let type_str = match day.day_type {
|
let type_str = match day.day_type {
|
||||||
DayType::Regular => String::new(),
|
DayType::Regular => String::new(),
|
||||||
|
|
@ -158,26 +147,26 @@ fn print_month(month: &MonthReport, mode: &DisplayMode) {
|
||||||
println!(" {}", light_line);
|
println!(" {}", light_line);
|
||||||
println!(
|
println!(
|
||||||
" Monthly: {:>7} {:>7} {:>6}",
|
" Monthly: {:>7} {:>7} {:>6}",
|
||||||
format_hours(month.total_expected(), mode),
|
format_hours(month.total_expected(), use_minutes),
|
||||||
format_hours(month.total_actual(), mode),
|
format_hours(month.total_actual(), use_minutes),
|
||||||
format_diff(month.diff(), mode)
|
format_diff(month.diff(), use_minutes)
|
||||||
);
|
);
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print the cumulative balance.
|
/// Print the cumulative balance.
|
||||||
fn print_cumulative_balance(balance: i64, mode: &DisplayMode) {
|
fn print_cumulative_balance(balance: i64, use_minutes: bool) {
|
||||||
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, mode)
|
format_diff(balance, use_minutes)
|
||||||
);
|
);
|
||||||
println!("{}", light_line);
|
println!("{}", light_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print warnings section.
|
/// Print warnings section.
|
||||||
fn print_warnings(report: &TimesheetReport, mode: &DisplayMode) {
|
fn print_warnings(report: &TimesheetReport, use_minutes: bool) {
|
||||||
if !report.has_warnings() {
|
if !report.has_warnings() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +231,7 @@ fn print_warnings(report: &TimesheetReport, mode: &DisplayMode) {
|
||||||
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, mode)
|
format_hours(*minutes_worked, use_minutes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -308,12 +297,7 @@ fn print_debug(report: &TimesheetReport, timesheets: &[Timesheet]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(decimal: bool, debug: bool) -> Result<(), StreamdError> {
|
pub fn run(use_minutes: 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);
|
||||||
|
|
||||||
|
|
@ -355,11 +339,11 @@ pub fn run(decimal: bool, debug: bool) -> Result<(), StreamdError> {
|
||||||
print_header();
|
print_header();
|
||||||
|
|
||||||
for month in &report.months {
|
for month in &report.months {
|
||||||
print_month(month, &mode);
|
print_month(month, use_minutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
print_cumulative_balance(report.cumulative_balance, &mode);
|
print_cumulative_balance(report.cumulative_balance, use_minutes);
|
||||||
print_warnings(&report, &mode);
|
print_warnings(&report, use_minutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -371,34 +355,33 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_hours_decimal() {
|
fn test_format_hours_decimal() {
|
||||||
assert_eq!(format_hours(480, &DisplayMode::Decimal), "8.00h");
|
assert_eq!(format_hours(480, false), "8.0h");
|
||||||
assert_eq!(format_hours(510, &DisplayMode::Decimal), "8.50h");
|
assert_eq!(format_hours(510, false), "8.5h");
|
||||||
assert_eq!(format_hours(507, &DisplayMode::Decimal), "8.45h");
|
assert_eq!(format_hours(0, false), "0.0h");
|
||||||
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, &DisplayMode::Minutes), "8:00");
|
assert_eq!(format_hours(480, true), "8:00");
|
||||||
assert_eq!(format_hours(510, &DisplayMode::Minutes), "8:30");
|
assert_eq!(format_hours(510, true), "8:30");
|
||||||
assert_eq!(format_hours(0, &DisplayMode::Minutes), "0:00");
|
assert_eq!(format_hours(0, true), "0:00");
|
||||||
assert_eq!(format_hours(75, &DisplayMode::Minutes), "1:15");
|
assert_eq!(format_hours(75, true), "1:15");
|
||||||
assert_eq!(format_hours(77, &DisplayMode::Minutes), "1:17");
|
assert_eq!(format_hours(77, true), "1:17");
|
||||||
assert_eq!(format_hours(200, &DisplayMode::Minutes), "3:20");
|
assert_eq!(format_hours(200, true), "3:20");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_diff_decimal() {
|
fn test_format_diff_decimal() {
|
||||||
assert_eq!(format_diff(30, &DisplayMode::Decimal), "+0.50h");
|
assert_eq!(format_diff(30, false), "+0.5h");
|
||||||
assert_eq!(format_diff(-90, &DisplayMode::Decimal), "-1.50h");
|
assert_eq!(format_diff(-90, false), "-1.5h");
|
||||||
assert_eq!(format_diff(0, &DisplayMode::Decimal), "+0.00h");
|
assert_eq!(format_diff(0, false), "+0.0h");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_diff_minutes() {
|
fn test_format_diff_minutes() {
|
||||||
assert_eq!(format_diff(30, &DisplayMode::Minutes), "+0:30");
|
assert_eq!(format_diff(30, true), "+0:30");
|
||||||
assert_eq!(format_diff(-90, &DisplayMode::Minutes), "-1:30");
|
assert_eq!(format_diff(-90, true), "-1:30");
|
||||||
assert_eq!(format_diff(0, &DisplayMode::Minutes), "+0:00");
|
assert_eq!(format_diff(0, true), "+0:00");
|
||||||
assert_eq!(format_diff(75, &DisplayMode::Minutes), "+1:15");
|
assert_eq!(format_diff(75, true), "+1:15");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { decimal, debug }) => {
|
Some(Commands::Timesheet { minutes, debug }) => {
|
||||||
streamd::cli::commands::timesheet::run(decimal, debug)?
|
streamd::cli::commands::timesheet::run(minutes, debug)?
|
||||||
}
|
}
|
||||||
Some(Commands::Completions { shell }) => {
|
Some(Commands::Completions { shell }) => {
|
||||||
streamd::cli::commands::completions::run(shell);
|
streamd::cli::commands::completions::run(shell);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue