From 927b603f27b80d6a7edd6c04d96e8d014ede9845 Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Sun, 25 Aug 2024 23:46:12 +0200 Subject: [PATCH] Moved output formats into own submodule --- git_time_tracker/__init__.py | 93 +++-------------------------- git_time_tracker/format/__init__.py | 1 + git_time_tracker/format/cli.py | 92 ++++++++++++++++++++++++++++ git_time_tracker/source/__init__.py | 1 + test/__init__.py | 0 5 files changed, 101 insertions(+), 86 deletions(-) create mode 100644 git_time_tracker/format/__init__.py create mode 100644 git_time_tracker/format/cli.py create mode 100644 test/__init__.py diff --git a/git_time_tracker/__init__.py b/git_time_tracker/__init__.py index 829ad70..ef88615 100644 --- a/git_time_tracker/__init__.py +++ b/git_time_tracker/__init__.py @@ -29,108 +29,29 @@ import argparse import datetime import logging import sys -from collections.abc import Iterator from pathlib import Path from .data import HIDDEN_LABEL_PREFIX, HIDDEN_LABEL_TOTAL, WorkSample +from .format import cli from .source import git_repo logger = logging.getLogger(__name__) DEFAULT_EST_TIME = datetime.timedelta(hours=1) - ZERO_DURATION = datetime.timedelta(seconds=0) HOUR = datetime.timedelta(hours=1) MINUTE = datetime.timedelta(minutes=1) -def fmt_year_ranges_internal(years: list[int]) -> Iterator[str]: - years = sorted(years) - for idx, year in enumerate(years): - at_end = idx == len(years) - 1 - range_before = idx > 0 and years[idx - 1] == year - 1 - range_after = not at_end and years[idx + 1] == year + 1 - - if not range_before or not range_after: - yield str(year) - - if not at_end: - if not range_before and range_after: - yield '-' - elif not range_after: - yield ',' - - -def fmt_year_ranges(years: list[int]) -> str: - return ''.join(list(fmt_year_ranges_internal(years))) - - -def fmt_line(label_type: str, label: str, total_time: datetime.timedelta) -> str: - hours = int(total_time / HOUR) - minutes = int((total_time - hours * HOUR) / MINUTE) - return f' {label_type:10} {label:40} {hours:-4d}h {minutes:-2d}m' - - -def generate_report( - samples: list[WorkSample], sample_filter=frozenset(), -) -> Iterator[str]: - LABEL_FILTER = {} - - # Time spent per label - time_per_label: dict[str, datetime.timedelta] = {} - years_per_label: dict[str, set[int]] = {} - prev_time = datetime.datetime.fromtimestamp(0, datetime.UTC) - for sample in samples: - est_time: datetime.timedelta = DEFAULT_EST_TIME - est_time = min(sample.registered_at - prev_time, est_time) - - if len(sample_filter) == 0: - pass - elif not set(sample.labels).intersection(sample_filter): - continue - - for label in sample.labels: - time_per_label.setdefault(label, ZERO_DURATION) - time_per_label[label] += est_time - years_per_label.setdefault(label, set()).add(sample.registered_at.year) - - prev_time = sample.registered_at - del sample, est_time - - time_and_label = [(duration, label) for label, duration in time_per_label.items()] - time_and_label.sort(reverse=True) - - # - yield '-' * 66 - yield '\n' - for total_time, label_and_type in time_and_label: - if label_and_type.startswith(HIDDEN_LABEL_PREFIX): - continue - - label_type, label = label_and_type.split(':', 1) - - if len(LABEL_FILTER) > 0 and label_type not in LABEL_FILTER: - continue - - yield fmt_line(label_type, label, total_time) - yield ' (' - yield fmt_year_ranges(years_per_label.get(label_and_type, [])) - yield ')' - yield '\n' - del label, total_time - - yield '-' * 66 - yield '\n' - - yield fmt_line('', 'TOTAL', time_per_label.get(HIDDEN_LABEL_TOTAL, ZERO_DURATION)) - yield '\n' - - def parse_arguments(): parser = argparse.ArgumentParser() parser.add_argument( - '--git-repo', action='extend', nargs='+', type=Path, dest='repositories', + '--git-repo', + action='extend', + nargs='+', + type=Path, + dest='repositories', ) parser.add_argument( '--filter', @@ -157,5 +78,5 @@ def main(): shared_time_stamps = sorted(shared_time_stamps) - for t in generate_report(shared_time_stamps, sample_filter=args.sample_filter): + for t in cli.generate_report(shared_time_stamps, sample_filter=args.sample_filter): sys.stdout.write(t) diff --git a/git_time_tracker/format/__init__.py b/git_time_tracker/format/__init__.py new file mode 100644 index 0000000..75e6e4e --- /dev/null +++ b/git_time_tracker/format/__init__.py @@ -0,0 +1 @@ +"""Submodule containing output formats.""" diff --git a/git_time_tracker/format/cli.py b/git_time_tracker/format/cli.py new file mode 100644 index 0000000..227dd4d --- /dev/null +++ b/git_time_tracker/format/cli.py @@ -0,0 +1,92 @@ +import datetime +from collections.abc import Iterator + +from ..data import HIDDEN_LABEL_PREFIX, HIDDEN_LABEL_TOTAL, WorkSample + +DEFAULT_EST_TIME = datetime.timedelta(hours=1) +ZERO_DURATION = datetime.timedelta(seconds=0) +HOUR = datetime.timedelta(hours=1) +MINUTE = datetime.timedelta(minutes=1) + + +def fmt_year_ranges_internal(years: list[int]) -> Iterator[str]: + years = sorted(years) + for idx, year in enumerate(years): + at_end = idx == len(years) - 1 + range_before = idx > 0 and years[idx - 1] == year - 1 + range_after = not at_end and years[idx + 1] == year + 1 + + if not range_before or not range_after: + yield str(year) + + if not at_end: + if not range_before and range_after: + yield '-' + elif not range_after: + yield ',' + + +def fmt_year_ranges(years: list[int]) -> str: + return ''.join(list(fmt_year_ranges_internal(years))) + + +def fmt_line(label_type: str, label: str, total_time: datetime.timedelta) -> str: + hours = int(total_time / HOUR) + minutes = int((total_time - hours * HOUR) / MINUTE) + return f' {label_type:10} {label:40} {hours:-4d}h {minutes:-2d}m' + + +def generate_report( + samples: list[WorkSample], + sample_filter=frozenset(), +) -> Iterator[str]: + LABEL_FILTER = {} + + # Time spent per label + time_per_label: dict[str, datetime.timedelta] = {} + years_per_label: dict[str, set[int]] = {} + prev_time = datetime.datetime.fromtimestamp(0, datetime.UTC) + for sample in samples: + est_time: datetime.timedelta = DEFAULT_EST_TIME + est_time = min(sample.registered_at - prev_time, est_time) + + if len(sample_filter) == 0: + pass + elif not set(sample.labels).intersection(sample_filter): + continue + + for label in sample.labels: + time_per_label.setdefault(label, ZERO_DURATION) + time_per_label[label] += est_time + years_per_label.setdefault(label, set()).add(sample.registered_at.year) + + prev_time = sample.registered_at + del sample, est_time + + time_and_label = [(duration, label) for label, duration in time_per_label.items()] + time_and_label.sort(reverse=True) + + # + yield '-' * 66 + yield '\n' + for total_time, label_and_type in time_and_label: + if label_and_type.startswith(HIDDEN_LABEL_PREFIX): + continue + + label_type, label = label_and_type.split(':', 1) + + if len(LABEL_FILTER) > 0 and label_type not in LABEL_FILTER: + continue + + yield fmt_line(label_type, label, total_time) + yield ' (' + yield fmt_year_ranges(years_per_label.get(label_and_type, [])) + yield ')' + yield '\n' + del label, total_time + + yield '-' * 66 + yield '\n' + + yield fmt_line('', 'TOTAL', time_per_label.get(HIDDEN_LABEL_TOTAL, ZERO_DURATION)) + yield '\n' diff --git a/git_time_tracker/source/__init__.py b/git_time_tracker/source/__init__.py index e69de29..0708e14 100644 --- a/git_time_tracker/source/__init__.py +++ b/git_time_tracker/source/__init__.py @@ -0,0 +1 @@ +"""Submodule containing input formats.""" diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29