Commit graph

19 commits

Author SHA1 Message Date
0204accd05
Wire up -o for stories; remove -o from status
- Extract _add_output_arg() helper; add it explicitly to summary, csv,
  and stories subparsers (removes it implicitly from status)
- _cmd_stories now honours args.output, redirecting print_stories output
  to the given file (same pattern as _cmd_summary)
- Update README with stories -o example
2026-06-02 09:31:14 +02:00
cd8ca789aa
Add --weekly flag to csv command
- Add `write_csv_weekly()` to output.py: writes entries from multiple
  days as a single CSV with one header row, correct date per row
- Add `-w`/`--weekly` flag to csv subparser
- _cmd_csv branches on args.weekly: fetches week sections, formats
  per-day date strings, calls write_csv_weekly; --raw is honoured
- Add TestWriteCsvWeekly with 6 tests
- Update README with weekly csv usage examples
2026-06-02 09:31:14 +02:00
985ee28113
Add --raw flag to csv command to skip aggregation
- Add `to_csv_entries()` to output.py: converts raw rows to write_csv
  entries one-for-one, without merging by (project, description)
- Add `--raw` flag to the csv subparser; _cmd_csv branches on it
- Add TestToCsvEntries with 6 tests
- Update README with --raw usage example
- Add .coverage and htmlcov/ to .gitignore
2026-06-02 09:31:13 +02:00
91ce81a65f
docs: require README updates alongside functionality changes 2026-06-02 09:31:12 +02:00
9f0a6e2027
feat(parser): resolve overlapping timesheet entries
Parallel work is logged as overlapping entries. resolve_overlaps()
splits the shared time equally using the midpoint of the overlap region:

- Partial overlap: the midpoint becomes the boundary between the two
  entries (earlier entry trimmed, later entry delayed).
- Full containment: the containing entry is split into two pieces
  surrounding the contained one, with the midpoint rule applied to
  the overlap region.

Open entries (no end time) are passed through unchanged.

resolve_overlaps() is called automatically in filter_rows_by_date,
filter_week_sections, and the --input single-day path in cli.py, so
all subcommands benefit without further changes.
2026-06-02 09:31:11 +02:00
f99e114770
docs: require TDD workflow in AGENTS.md
New functionality must follow the red-green-refactor cycle:
1. Write failing tests first
2. Confirm they fail before implementing
3. Implement until tests pass
4. Run full suite to check for regressions
2026-06-02 09:31:11 +02:00
17e6a35b37
docs: move project description to README, point AGENTS.md to it 2026-06-02 09:31:10 +02:00
2d60624e0e
feat(stories): add stories subcommand
- Add stories subcommand listing stories worked on, grouped by project
- Preserve story_raw in parser row dicts alongside the stripped story,
  so markdown links are available for display
- print_stories() filters to rows with a non-empty story field,
  deduplicates by stripped story text (preferring the linked version),
  sums hours per story, and outputs an indented Markdown list
- Project names resolved through project_map (same as csv/summary)
- -w/--weekly flag aggregates stories across the full week
- Add tests for print_stories covering deduplication, link preservation,
  grouping, empty rows, and story-less row exclusion
- Fix flex daily target in status: use projected hours per prior day
  rather than fixed 8h when computing remaining hours for today
2026-06-02 09:31:09 +02:00
f372a691d4
feat(status): add status subcommand with day and week metrics
- Add status.py with compute_day_status() and compute_week_status()
- Add projected_hours_for_day(): for each entry, use its duration if
  closed, or the next entry's start time as the close time if open
- Open entries (start present, end absent) are preserved by the parser
  instead of being skipped; aggregate_rows() skips them for summaries
- Expected end is computed by filling remaining hours into available
  time slots from the open entry's start; clamped to latest_end if a
  pre-logged entry ends later, with a note explaining why
- Projected week total sums projected_hours_for_day() across all days
- Add status subcommand to cli.py with shared source/day arguments
- Add [work] daily_hours / weekly_hours config keys (default 8 / 40)
- Add timesheets.example.toml [work] section
- Add tests for projected_hours_for_day, compute_day_status,
  compute_week_status and all DayStatus/WeekStatus fields
2026-06-02 09:31:08 +02:00
d5dbe8791b
fix(parser): allow blank lines within a table block
Blank (whitespace-only) lines inside a table no longer split it into
separate blocks. They are buffered and discarded if more table rows
follow, enabling patterns like pre-filling a recurring meeting entry
with a blank line separating it from the rest of the day's entries.
2026-06-02 09:31:07 +02:00
ac1e9f959a
feat(summary): add --weekly/-w and --short/-s flags
--weekly (-w): show the summary for the entire week containing the
given day, fetching from Joplin or parsing all tables in the file

--short (-s, repeatable):
  -s alone:       one line per project label + total
  -s --weekly:    per-day project totals with day subtotals
  -ss --weekly:   one line per day with right-aligned date + week total

Add filter_week_sections() to parser.py to split a document into
(date, rows) pairs for a given ISO week. Add print_summary_short(),
print_summary_weekly(), print_summary_weekly_short(), and
print_summary_weekly_totals() to output.py.
2026-06-02 09:31:07 +02:00
6915d8d764
refactor(cli): split into summary and csv subcommands
- Add subparsers for 'summary' and 'csv' replacing the --summary flag
- Extract _add_shared_args(), _resolve_date(), _resolve_rows(), and
  _resolve_project_map() as shared helpers used by both subcommands
- Both subcommands support -o/--output to write to a file instead of stdout
- Update AGENTS.md with new subcommand usage examples
2026-06-02 09:31:06 +02:00
615bfe30e0
feat(cli): add natural language date parsing via dateparser
- Add _parse_natural() to utils.py using dateparser as a fallback when
  structured date formats (YYYY-MM-DD, MM-DD, DD-MM) don't match
- Supports expressions like 'today', 'yesterday', 'monday', '3 days ago'
- Change day argument to nargs='*' and join tokens so unquoted
  multi-word expressions like: uv run timesheets 3 days ago work correctly
- Pin dateparser to English to avoid locale-dependent behaviour
- Update tests to cover natural language cases and fix test_last_monday
  (dateparser does not support 'last monday'; use 'monday' instead)
2026-06-02 09:31:05 +02:00
29698b1241
feat(config): add TOML config file support
- Add config.py with load_config(), find_default_config(), get_token(),
  and get_map_path()
- Auto-discover timesheets.toml in cwd; override with --config flag
- Priority: CLI flag > config file > env var / cwd default
- Add timesheets.example.toml as a committed reference template
- Add timesheets.toml to .gitignore to prevent accidental secret leakage
- Document config file format in AGENTS.md
2026-06-02 09:31:04 +02:00
267ad5b1b5
feat(cli): add flexible positional day argument
- Add parse_date_arg() to utils.py supporting YYYY-MM-DD, MM-DD, and
  DD-MM formats with either - or / as separator
- Add AmbiguousDateError for two-part dates valid as both MM-DD and DD-MM
- Replace --day flag with a positional optional argument (defaults to today)
- Remove old _parse_date() helper from cli.py
2026-06-02 09:31:04 +02:00
715e0988dc
refactor(cli): replace positional input argument with --input / -i flag 2026-06-02 09:31:03 +02:00
ecdd28e8a3
feat(joplin): add --joplin flag to fetch weekly timesheet note from Joplin
- Add joplin.py with fetch_week_note() that walks Work > Timesheets > YYYY
  and returns the body of the matching YYYY-WNN note via joppy ClientApi
- Add filter_rows_by_date() to parser.py to extract only rows belonging
  to a specific day based on '# ... YYYY-MM-DD' headings in the document
- Update cli.py: input and --joplin are now a mutually exclusive required
  group; add --token flag with JOPLIN_TOKEN env var fallback; --date is
  parsed into a real date object used for both output and day filtering
- Add joppy as a runtime dependency (lazy-imported in cli.py)
- Add tests for filter_rows_by_date and full mocked coverage of joplin.py
- Update AGENTS.md with Joplin usage, notebook structure, and test rules

The actual Joplin structure has notes directly inside the year notebook
(Work > Timesheets > YYYY), not in per-week sub-notebooks as initially
assumed. fetch_week_note() reflects this flat structure.
2026-06-02 09:31:02 +02:00
d6689a6c83
feat(parser): support multiple tables in a single markdown document
- Add extract_table_blocks() to split a document into contiguous table
  blocks, ignoring prose, headings, and blank lines between them
- Add parse_document() as the new top-level entry point that runs
  extract_table_blocks + detect_has_duration_column + parse_table per
  block and returns a combined flat list of rows
- Guard against empty End cells (e.g. in-progress rows) by validating
  the end field before calculating duration
- Update cli.py to use parse_document() instead of the manual
  detect + parse combo
- Add tests for extract_table_blocks and parse_document, including two
  smoke tests against the real 2026-W21 weekly timesheet file
2026-06-02 09:31:02 +02:00
7bea08ddac
feat: set up modularized version of project with testing 2026-06-02 09:31:01 +02:00