1
0

Compare commits

..

No commits in common. "b5c15f5d9a0adb079092983d3cedd990fc9df513" and "11888b4208f06c60bb24ab2256e2a20a0c58b8c6" have entirely different histories.

6 changed files with 57 additions and 134 deletions

View File

@ -31,20 +31,7 @@ And the ([Hamster](https://github.com/projecthamster/hamster)) manual time track
![](docs/obligatory-hamster.png)
## Dependencies
All requirements can be installed easily using:
```bash
pip install -r requirements.txt
```
Full list of requirements:
- [GitPython](https://pypi.org/project/GitPython/)
- [icalendar](https://pypi.org/project/icalendar/)
## License
# License
```
MIT License

View File

@ -39,7 +39,7 @@ from .data import (
WorkSample,
)
from .format import cli, icalendar
from .source import csv_file, git_repo
from .source import git_repo, csv_file
logger = logging.getLogger(__name__)
@ -51,8 +51,7 @@ MINUTE = datetime.timedelta(minutes=1)
def filter_samples(
samples: list[WorkSample],
sample_filter: set[str],
samples: list[WorkSample], sample_filter: set[str],
) -> list[WorkSample]:
assert len(sample_filter) > 0
return [s for s in samples if set(s.labels).intersection(sample_filter)]
@ -67,16 +66,10 @@ def heuristically_realize_samples(
* No samples overlap.
"""
previous_sample_end = None
previous_sample_end = datetime.datetime.fromtimestamp(0, datetime.UTC)
for sample in samples:
end_at = sample.end_at
if previous_sample_end is None:
if end_at.tzinfo:
previous_sample_end = datetime.datetime.fromtimestamp(0, datetime.UTC)
else:
previous_sample_end = datetime.datetime.fromtimestamp(0)
assert previous_sample_end <= end_at, 'Iterating in incorrect order'
# TODO: Allow end_at is None
@ -127,17 +120,9 @@ def parse_arguments():
default='cli_report',
choices=['cli_report', 'icalendar'],
)
parser.add_argument(
'--out',
action='store',
type=Path,
dest='output_file',
default='output/samples.ics',
)
return parser.parse_args()
def load_samples(args) -> set[WorkSample]:
def load_samples(args):
shared_time_stamps_set: set[WorkSample] = set()
# Git repositories
@ -156,8 +141,8 @@ def load_samples(args) -> set[WorkSample]:
)
del csv_path
return shared_time_stamps_set
return shared_time_stamps_set
def main():
logging.basicConfig()
@ -188,6 +173,5 @@ def main():
sys.stdout.write(t)
elif args.format_mode == 'icalendar':
icalendar.generate_icalendar_file(
shared_time_stamps,
file=args.output_file,
shared_time_stamps, file='./output/samples.ics',
)

View File

@ -1 +1 @@
__version__ = '0.1.19'
__version__ = '0.1.18'

View File

@ -47,8 +47,7 @@ def generate_calendar(
for label_and_type in sample.labels:
if label_and_type.startswith('author:'):
event.add(
'organizer',
'mailto:' + label_and_type.removeprefix('author:'),
'organizer', 'mailto:' + label_and_type.removeprefix('author:'),
)
cal.add_component(event)

View File

@ -1,102 +1,60 @@
import datetime
import urllib.parse
from typing import Any
import argparse
from collections.abc import Iterator
from decimal import Decimal
import datetime
import urllib.parse
from pathlib import Path
import dataclasses
from personal_data.util import load_csv_file
from ..data import WorkSample
@dataclasses.dataclass
class PossibleKeys:
time_start: list[str]
time_end: list[str]
duration: list[str]
name: list[str]
image: list[str]
misc: list[str]
def iterate_samples_from_dicts(rows: list[dict]) -> Iterator[WorkSample]:
max_title_parts = 2
for event_data in rows:
def determine_possible_keys(event_data: dict[str, Any]) -> PossibleKeys:
# Select data
time_keys = [
possible_time_keys = [
k for k, v in event_data.items() if isinstance(v, datetime.date)
]
duration_keys = [
k
for k, v in event_data.items()
if isinstance(v, Decimal) and 'duration_seconds' in k
possible_duration_keys = [
k for k, v in event_data.items() if isinstance(v, Decimal) and 'duration_seconds' in k
]
name_keys = [k for k, v in event_data.items() if isinstance(v, str)]
image_keys = [
possible_name_keys = [k for k, v in event_data.items() if isinstance(v, str)]
possible_image_keys = [
k for k, v in event_data.items() if isinstance(v, urllib.parse.ParseResult)
]
misc_keys = list(event_data.keys())
for k in image_keys:
if k in misc_keys:
misc_keys.remove(k)
possible_misc_keys = list(event_data.keys())
for k in possible_image_keys:
if k in possible_misc_keys:
possible_misc_keys.remove(k)
del k
for k in time_keys:
if k in misc_keys:
misc_keys.remove(k)
for k in possible_time_keys :
if k in possible_misc_keys:
possible_misc_keys.remove(k)
del k
time_start_keys = [k for k in time_keys if 'start' in k.lower() ]
time_end_keys = [k for k in time_keys if 'end' in k.lower() or 'stop' in k.lower() ]
date = event_data[possible_time_keys[0]] if possible_time_keys else None
image = event_data[possible_image_keys[0]] if possible_image_keys else None
return PossibleKeys(
time_start = time_start_keys,
time_end = time_end_keys,
duration = duration_keys,
name = name_keys,
image = image_keys,
misc = misc_keys,
)
if date is None:
continue
def start_end(sample: dict[str,Any], keys: PossibleKeys) -> tuple[datetime.datetime | None, datetime.datetime | None]:
if keys.time_start and keys.time_end:
return (sample[keys.time_start[0]], sample[keys.time_end[0]])
if len(possible_duration_keys) > 0:
start_at = date
seconds = event_data[possible_duration_keys[0]]
end_at = date + datetime.timedelta(seconds = float(seconds))
del seconds
else:
start_at = None
end_at = date
if keys.time_start and keys.duration:
start = sample[keys.time_start[0]]
duration = datetime.timedelta(seconds=float(sample[keys.duration[0]]))
return (start, start + duration)
if keys.time_start:
start = sample[keys.time_start[0]]
return (start, None)
if keys.time_end:
return (None, sample[keys.time_end[0]])
return (None, None)
def iterate_samples_from_dicts(rows: list[dict[str,Any]]) -> Iterator[WorkSample]:
assert len(rows) > 0
max_title_parts = 2
if True:
event_data = rows[len(rows)//2] # Hopefully select a useful representative.
possible_keys = determine_possible_keys(event_data)
del event_data
assert len(possible_keys.time_start) + len(possible_keys.time_end) >= 1
assert len(possible_keys.image) >= 0
for event_data in rows:
'''
title = ': '.join(event_data[k] for k in possible_name_keys[:max_title_parts])
description = '\n\n'.join(
event_data[k] for k in possible_name_keys[max_title_parts:]
)
image = event_data[possible_keys.image[0]] if possible_keys.image else None
'''
description = '\n\n'.join(event_data[k] for k in possible_name_keys[max_title_parts:])
(start_at, end_at) = start_end(event_data, possible_keys)
labels = [f'{k}:{event_data[k]}' for k in possible_keys.misc]
labels = [f'{k}:{event_data[k]}' for k in possible_misc_keys]
# Create event
yield WorkSample(
@ -107,9 +65,6 @@ def iterate_samples_from_dicts(rows: list[dict[str,Any]]) -> Iterator[WorkSample
del event_data
def iterate_samples_from_csv_file(file_path: Path) -> Iterator[WorkSample]:
dicts = load_csv_file(file_path)
samples = list(iterate_samples_from_dicts(dicts))
assert len(samples) > 0, 'Did not found any samples'
yield from samples
yield from iterate_samples_from_dicts(dicts)

View File

@ -39,12 +39,10 @@ def get_samples_from_project(repo: git.Repo) -> Iterator[WorkSample]:
labels.append('author:' + commit.author.email)
authored_date = datetime.datetime.fromtimestamp(
commit.authored_date,
tz=datetime.UTC,
commit.authored_date, tz=datetime.UTC,
)
committed_date = datetime.datetime.fromtimestamp(
commit.committed_date,
tz=datetime.UTC,
commit.committed_date, tz=datetime.UTC,
)
yield WorkSample(