diff options
Diffstat (limited to 'overlays')
| -rwxr-xr-x | overlays/worktime/worktime/__main__.py | 170 |
1 files changed, 134 insertions, 36 deletions
diff --git a/overlays/worktime/worktime/__main__.py b/overlays/worktime/worktime/__main__.py index 2dc9ed72..9bc7ac3b 100755 --- a/overlays/worktime/worktime/__main__.py +++ b/overlays/worktime/worktime/__main__.py | |||
| @@ -26,12 +26,14 @@ from sys import stderr | |||
| 26 | from tabulate import tabulate | 26 | from tabulate import tabulate |
| 27 | 27 | ||
| 28 | from itertools import groupby | 28 | from itertools import groupby |
| 29 | from functools import cache | 29 | from functools import cache, partial |
| 30 | 30 | ||
| 31 | import backoff | 31 | import backoff |
| 32 | 32 | ||
| 33 | from pathlib import Path | 33 | from pathlib import Path |
| 34 | 34 | ||
| 35 | from collections import defaultdict | ||
| 36 | |||
| 35 | 37 | ||
| 36 | class TogglAPISection(Enum): | 38 | class TogglAPISection(Enum): |
| 37 | TOGGL = '/api/v8' | 39 | TOGGL = '/api/v8' |
| @@ -168,7 +170,7 @@ class Worktime(object): | |||
| 168 | running_entry = None | 170 | running_entry = None |
| 169 | now = datetime.now(tzlocal()) | 171 | now = datetime.now(tzlocal()) |
| 170 | time_pulled_forward = timedelta() | 172 | time_pulled_forward = timedelta() |
| 171 | is_workday = False | 173 | now_is_workday = False |
| 172 | include_running = True | 174 | include_running = True |
| 173 | time_to_work = None | 175 | time_to_work = None |
| 174 | force_day_to_work = True | 176 | force_day_to_work = True |
| @@ -213,6 +215,9 @@ class Worktime(object): | |||
| 213 | def would_be_workday(self, date): | 215 | def would_be_workday(self, date): |
| 214 | return date.isoweekday() in self.workdays and date not in set(day for (day, val) in Worktime.holidays(date.year).items() if val >= 1) | 216 | return date.isoweekday() in self.workdays and date not in set(day for (day, val) in Worktime.holidays(date.year).items() if val >= 1) |
| 215 | 217 | ||
| 218 | def is_workday(self, date, extra=True): | ||
| 219 | return date in self.days_to_work or (extra and date in self.extra_days_to_work) | ||
| 220 | |||
| 216 | def __init__(self, start_datetime=None, end_datetime=None, now=None, include_running=True, force_day_to_work=True, **kwargs): | 221 | def __init__(self, start_datetime=None, end_datetime=None, now=None, include_running=True, force_day_to_work=True, **kwargs): |
| 217 | self.include_running = include_running | 222 | self.include_running = include_running |
| 218 | self.force_day_to_work = force_day_to_work | 223 | self.force_day_to_work = force_day_to_work |
| @@ -229,8 +234,8 @@ class Worktime(object): | |||
| 229 | ) | 234 | ) |
| 230 | date_format = config.get("WORKTIME", {}).get("DateFormat", '%Y-%m-%d') | 235 | date_format = config.get("WORKTIME", {}).get("DateFormat", '%Y-%m-%d') |
| 231 | 236 | ||
| 232 | start_date = start_datetime or datetime.strptime(config.get("WORKTIME", {}).get("StartDate"), date_format).replace(tzinfo=tzlocal()) | 237 | self.start_date = start_datetime or datetime.strptime(config.get("WORKTIME", {}).get("StartDate"), date_format).replace(tzinfo=tzlocal()) |
| 233 | end_date = end_datetime or self.now | 238 | self.end_date = end_datetime or self.now |
| 234 | 239 | ||
| 235 | try: | 240 | try: |
| 236 | with open(Path(config_dir) / "reset", 'r') as reset: | 241 | with open(Path(config_dir) / "reset", 'r') as reset: |
| @@ -238,8 +243,8 @@ class Worktime(object): | |||
| 238 | stripped_line = line.strip() | 243 | stripped_line = line.strip() |
| 239 | reset_date = datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()) | 244 | reset_date = datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()) |
| 240 | 245 | ||
| 241 | if reset_date > start_date and reset_date <= end_date: | 246 | if reset_date > self.start_date and reset_date <= self.end_date: |
| 242 | start_date = reset_date | 247 | self.start_date = reset_date |
| 243 | except IOError as e: | 248 | except IOError as e: |
| 244 | if e.errno != 2: | 249 | if e.errno != 2: |
| 245 | raise e | 250 | raise e |
| @@ -252,11 +257,11 @@ class Worktime(object): | |||
| 252 | holidays = dict() | 257 | holidays = dict() |
| 253 | 258 | ||
| 254 | leave_per_year = int(config.get("WORKTIME", {}).get("LeavePerYear", 30)) | 259 | leave_per_year = int(config.get("WORKTIME", {}).get("LeavePerYear", 30)) |
| 255 | for year in range(start_date.year, end_date.year + 1): | 260 | for year in range(self.start_date.year, self.end_date.year + 1): |
| 256 | holidays |= {k: v * self.time_per_day for k, v in Worktime.holidays(year).items()} | 261 | holidays |= {k: v * self.time_per_day for k, v in Worktime.holidays(year).items()} |
| 257 | leave_frac = 1 | 262 | leave_frac = 1 |
| 258 | if date(year, 1, 1) < start_date.date(): | 263 | if date(year, 1, 1) < self.start_date.date(): |
| 259 | leave_frac = (date(year + 1, 1, 1) - start_date.date()) / (date(year + 1, 1, 1) - date(year, 1, 1)) | 264 | leave_frac = (date(year + 1, 1, 1) - self.start_date.date()) / (date(year + 1, 1, 1) - date(year, 1, 1)) |
| 260 | self.leave_budget |= {year: floor(leave_per_year * leave_frac)} | 265 | self.leave_budget |= {year: floor(leave_per_year * leave_frac)} |
| 261 | 266 | ||
| 262 | try: | 267 | try: |
| @@ -266,7 +271,7 @@ class Worktime(object): | |||
| 266 | if stripped_line: | 271 | if stripped_line: |
| 267 | [datestr, count] = stripped_line.split(' ') | 272 | [datestr, count] = stripped_line.split(' ') |
| 268 | day = datetime.strptime(datestr, date_format).replace(tzinfo=tzlocal()).date() | 273 | day = datetime.strptime(datestr, date_format).replace(tzinfo=tzlocal()).date() |
| 269 | if day != start_date.date(): | 274 | if day != self.start_date.date(): |
| 270 | continue | 275 | continue |
| 271 | 276 | ||
| 272 | self.leave_budget[day.year] = (self.leave_budget[day.year] if day.year in self.leave_budget else 0) + int(count) | 277 | self.leave_budget[day.year] = (self.leave_budget[day.year] if day.year in self.leave_budget else 0) + int(count) |
| @@ -302,7 +307,7 @@ class Worktime(object): | |||
| 302 | parse_datestr(stripped_line) | 307 | parse_datestr(stripped_line) |
| 303 | 308 | ||
| 304 | for day in [fromDay + timedelta(days = x) for x in range(0, (toDay - fromDay).days + 1)]: | 309 | for day in [fromDay + timedelta(days = x) for x in range(0, (toDay - fromDay).days + 1)]: |
| 305 | if end_date.date() < day or day < start_date.date(): | 310 | if self.end_date.date() < day or day < self.start_date.date(): |
| 306 | continue | 311 | continue |
| 307 | 312 | ||
| 308 | if excused_kind == 'leave' and self.would_be_workday(day): | 313 | if excused_kind == 'leave' and self.would_be_workday(day): |
| @@ -314,8 +319,8 @@ class Worktime(object): | |||
| 314 | 319 | ||
| 315 | pull_forward = dict() | 320 | pull_forward = dict() |
| 316 | 321 | ||
| 317 | start_day = start_date.date() | 322 | start_day = self.start_date.date() |
| 318 | end_day = end_date.date() | 323 | end_day = self.end_date.date() |
| 319 | 324 | ||
| 320 | try: | 325 | try: |
| 321 | with open(Path(config_dir) / "pull-forward", 'r') as excused: | 326 | with open(Path(config_dir) / "pull-forward", 'r') as excused: |
| @@ -339,13 +344,13 @@ class Worktime(object): | |||
| 339 | else: | 344 | else: |
| 340 | if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break | 345 | if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break |
| 341 | else: | 346 | else: |
| 342 | if d >= end_date.date(): | 347 | if d >= self.end_date.date(): |
| 343 | pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day - (holidays[d] if d in holidays else timedelta())) | 348 | pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day - (holidays[d] if d in holidays else timedelta())) |
| 344 | except IOError as e: | 349 | except IOError as e: |
| 345 | if e.errno != 2: | 350 | if e.errno != 2: |
| 346 | raise e | 351 | raise e |
| 347 | 352 | ||
| 348 | days_to_work = dict() | 353 | self.days_to_work = dict() |
| 349 | 354 | ||
| 350 | if pull_forward: | 355 | if pull_forward: |
| 351 | end_day = max(end_day, max(list(pull_forward))) | 356 | end_day = max(end_day, max(list(pull_forward))) |
| @@ -356,9 +361,9 @@ class Worktime(object): | |||
| 356 | if day in holidays.keys(): | 361 | if day in holidays.keys(): |
| 357 | time_to_work -= holidays[day] | 362 | time_to_work -= holidays[day] |
| 358 | if time_to_work > timedelta(): | 363 | if time_to_work > timedelta(): |
| 359 | days_to_work[day] = time_to_work | 364 | self.days_to_work[day] = time_to_work |
| 360 | 365 | ||
| 361 | extra_days_to_work = dict() | 366 | self.extra_days_to_work = dict() |
| 362 | 367 | ||
| 363 | try: | 368 | try: |
| 364 | with open(Path(config_dir) / "days-to-work", 'r') as extra_days_to_work_file: | 369 | with open(Path(config_dir) / "days-to-work", 'r') as extra_days_to_work_file: |
| @@ -369,15 +374,15 @@ class Worktime(object): | |||
| 369 | if len(splitLine) == 2: | 374 | if len(splitLine) == 2: |
| 370 | [hours, datestr] = splitLine | 375 | [hours, datestr] = splitLine |
| 371 | day = datetime.strptime(datestr, date_format).replace(tzinfo=tzlocal()).date() | 376 | day = datetime.strptime(datestr, date_format).replace(tzinfo=tzlocal()).date() |
| 372 | extra_days_to_work[day] = timedelta(hours = float(hours)) | 377 | self.extra_days_to_work[day] = timedelta(hours = float(hours)) |
| 373 | else: | 378 | else: |
| 374 | extra_days_to_work[datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()] = self.time_per_day | 379 | self.extra_days_to_work[datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()] = self.time_per_day |
| 375 | except IOError as e: | 380 | except IOError as e: |
| 376 | if e.errno != 2: | 381 | if e.errno != 2: |
| 377 | raise e | 382 | raise e |
| 378 | 383 | ||
| 379 | 384 | ||
| 380 | self.is_workday = self.now.date() in days_to_work or self.now.date() in extra_days_to_work | 385 | self.now_is_workday = self.is_workday(self.now.date()) |
| 381 | 386 | ||
| 382 | self.time_worked = timedelta() | 387 | self.time_worked = timedelta() |
| 383 | 388 | ||
| @@ -387,37 +392,37 @@ class Worktime(object): | |||
| 387 | if self.running_entry: | 392 | if self.running_entry: |
| 388 | self.time_worked += self.running_entry | 393 | self.time_worked += self.running_entry |
| 389 | 394 | ||
| 390 | if self.running_entry and self.include_running and self.force_day_to_work and not (self.now.date() in days_to_work or self.now.date() in extra_days_to_work): | 395 | if self.running_entry and self.include_running and self.force_day_to_work and not (self.now.date() in self.days_to_work or self.now.date() in self.extra_days_to_work): |
| 391 | extra_days_to_work[self.now.date()] = timedelta() | 396 | self.extra_days_to_work[self.now.date()] = timedelta() |
| 392 | 397 | ||
| 393 | self.time_to_work = sum([days_to_work[day] for day in days_to_work.keys() if day <= end_date.date()], timedelta()) | 398 | self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta()) |
| 394 | for day in [d for d in list(pull_forward) if d > end_date.date()]: | 399 | for day in [d for d in list(pull_forward) if d > self.end_date.date()]: |
| 395 | days_forward = set([d for d in days_to_work.keys() if d >= end_date.date() and d < day and (not d in pull_forward or d == end_date.date())]) | 400 | days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) |
| 396 | extra_days_forward = set([d for d in extra_days_to_work.keys() if d >= end_date.date() and d < day and (not d in pull_forward or d == end_date.date())]) | 401 | extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) |
| 397 | days_forward = days_forward.union(extra_days_forward) | 402 | days_forward = days_forward.union(extra_days_forward) |
| 398 | 403 | ||
| 399 | extra_day_time_left = timedelta() | 404 | extra_day_time_left = timedelta() |
| 400 | for extra_day in extra_days_forward: | 405 | for extra_day in extra_days_forward: |
| 401 | day_time = max(timedelta(), self.time_per_day - extra_days_to_work[extra_day]) | 406 | day_time = max(timedelta(), self.time_per_day - self.extra_days_to_work[extra_day]) |
| 402 | extra_day_time_left += day_time | 407 | extra_day_time_left += day_time |
| 403 | extra_day_time = min(extra_day_time_left, pull_forward[day]) | 408 | extra_day_time = min(extra_day_time_left, pull_forward[day]) |
| 404 | time_forward = pull_forward[day] - extra_day_time | 409 | time_forward = pull_forward[day] - extra_day_time |
| 405 | if extra_day_time_left > timedelta(): | 410 | if extra_day_time_left > timedelta(): |
| 406 | for extra_day in extra_days_forward: | 411 | for extra_day in extra_days_forward: |
| 407 | day_time = max(timedelta(), self.time_per_day - extra_days_to_work[extra_day]) | 412 | day_time = max(timedelta(), self.time_per_day - self.extra_days_to_work[extra_day]) |
| 408 | extra_days_to_work[extra_day] += extra_day_time * (day_time / extra_day_time_left) | 413 | self.extra_days_to_work[extra_day] += extra_day_time * (day_time / extra_day_time_left) |
| 409 | 414 | ||
| 410 | hours_per_day_forward = time_forward / len(days_forward) if len(days_forward) > 0 else timedelta() | 415 | hours_per_day_forward = time_forward / len(days_forward) if len(days_forward) > 0 else timedelta() |
| 411 | days_forward.discard(end_date.date()) | 416 | days_forward.discard(self.end_date.date()) |
| 412 | 417 | ||
| 413 | self.time_pulled_forward += time_forward - hours_per_day_forward * len(days_forward) | 418 | self.time_pulled_forward += time_forward - hours_per_day_forward * len(days_forward) |
| 414 | 419 | ||
| 415 | if end_date.date() in extra_days_to_work: | 420 | if self.end_date.date() in self.extra_days_to_work: |
| 416 | self.time_pulled_forward += extra_days_to_work[end_date.date()] | 421 | self.time_pulled_forward += self.extra_days_to_work[self.end_date.date()] |
| 417 | 422 | ||
| 418 | self.time_to_work += self.time_pulled_forward | 423 | self.time_to_work += self.time_pulled_forward |
| 419 | 424 | ||
| 420 | self.time_worked += api.get_billable_hours(start_date, self.now, rounding = config.get("WORKTIME", {}).get("rounding", True)) | 425 | self.time_worked += api.get_billable_hours(self.start_date, self.now, rounding = config.get("WORKTIME", {}).get("rounding", True)) |
| 421 | 426 | ||
| 422 | def worktime(**args): | 427 | def worktime(**args): |
| 423 | worktime = Worktime(**args) | 428 | worktime = Worktime(**args) |
| @@ -458,7 +463,7 @@ def worktime(**args): | |||
| 458 | return f"{indicator}{difference_string}" | 463 | return f"{indicator}{difference_string}" |
| 459 | else: | 464 | else: |
| 460 | difference_string = difference_string(total_minutes_difference * timedelta(minutes = 1)) | 465 | difference_string = difference_string(total_minutes_difference * timedelta(minutes = 1)) |
| 461 | if worktime.is_workday: | 466 | if worktime.now_is_workday: |
| 462 | return difference_string | 467 | return difference_string |
| 463 | else: | 468 | else: |
| 464 | return f"({difference_string})" | 469 | return f"({difference_string})" |
| @@ -498,7 +503,7 @@ def time_worked(now, **args): | |||
| 498 | 503 | ||
| 499 | clockout_time = None | 504 | clockout_time = None |
| 500 | clockout_difference = None | 505 | clockout_difference = None |
| 501 | if then.is_workday or now.is_workday: | 506 | if then.now_is_workday or now.now_is_workday: |
| 502 | target_time = max(then.time_per_day, now.time_per_day) if then.time_per_day and now.time_per_day else (then.time_per_day if then.time_per_day else now.time_per_day); | 507 | target_time = max(then.time_per_day, now.time_per_day) if then.time_per_day and now.time_per_day else (then.time_per_day if then.time_per_day else now.time_per_day); |
| 503 | difference = target_time - worked | 508 | difference = target_time - worked |
| 504 | clockout_difference = 5 * ceil(difference / timedelta(minutes = 5)) | 509 | clockout_difference = 5 * ceil(difference / timedelta(minutes = 5)) |
| @@ -543,6 +548,8 @@ def leave(year, table, **args): | |||
| 543 | if leave_expires: | 548 | if leave_expires: |
| 544 | leave_expires = datetime.strptime(leave_expires, '%m-%d').date() | 549 | leave_expires = datetime.strptime(leave_expires, '%m-%d').date() |
| 545 | 550 | ||
| 551 | days = [worktime.start_date.date() + timedelta(days = x) for x in range(0, (worktime.end_date.date() - worktime.start_date.date()).days + 1)] | ||
| 552 | |||
| 546 | leave_budget = deepcopy(worktime.leave_budget) | 553 | leave_budget = deepcopy(worktime.leave_budget) |
| 547 | year_leave_budget = deepcopy(worktime.leave_budget) if year else None | 554 | year_leave_budget = deepcopy(worktime.leave_budget) if year else None |
| 548 | years = sorted(leave_budget.keys()) | 555 | years = sorted(leave_budget.keys()) |
| @@ -589,12 +596,99 @@ def leave(year, table, **args): | |||
| 589 | table_data = [] | 596 | table_data = [] |
| 590 | for year, days in leave_budget.items(): | 597 | for year, days in leave_budget.items(): |
| 591 | leave_days = sorted([day for day in worktime.leave_days if day.year == year]) | 598 | leave_days = sorted([day for day in worktime.leave_days if day.year == year]) |
| 592 | table_data += [[year, days, ','.join(map(lambda d: d.strftime('%m-%d'), leave_days))]] | 599 | would_be_workdays = [day for day in days if day.year == year and worktime.would_be_workday(day)] |
| 600 | table_data += [[year, days, f"{len(leave_days)}/{len(list(would_be_workdays))}", ','.join(map(lambda d: d.strftime('%m-%d'), leave_days))]] | ||
| 593 | print(tabulate(table_data, tablefmt="plain")) | 601 | print(tabulate(table_data, tablefmt="plain")) |
| 594 | else: | 602 | else: |
| 595 | print(leave_budget[year if year else def_year]) | 603 | print(leave_budget[year if year else def_year]) |
| 596 | 604 | ||
| 605 | def classification(classification_name, table, **args): | ||
| 606 | worktime = Worktime(**args) | ||
| 607 | config = Worktime.config() | ||
| 608 | date_format = config.get("WORKTIME", {}).get("DateFormat", '%Y-%m-%d') | ||
| 609 | config_dir = BaseDirectory.load_first_config('worktime') | ||
| 610 | days = [worktime.start_date.date() + timedelta(days = x) for x in range(0, (worktime.end_date.date() - worktime.start_date.date()).days + 1)] | ||
| 611 | |||
| 612 | year_classification = defaultdict(dict) | ||
| 613 | year_offset = defaultdict(lambda: 0) | ||
| 614 | |||
| 615 | extra_classified = dict() | ||
| 616 | classification_files = { | ||
| 617 | Path(config_dir) / classification_name: True, | ||
| 618 | Path(config_dir) / f"extra-{classification_name}": True, | ||
| 619 | Path(config_dir) / f"not-{classification_name}": False, | ||
| 620 | } | ||
| 621 | for path, val in classification_files.items(): | ||
| 622 | try: | ||
| 623 | with path.open('r') as fh: | ||
| 624 | for line in fh: | ||
| 625 | stripped_line = line.strip() | ||
| 626 | if stripped_line: | ||
| 627 | fromDay = toDay = None | ||
| 628 | def parse_single(singlestr): | ||
| 629 | return datetime.strptime(singlestr, date_format).replace(tzinfo=tzlocal()).date() | ||
| 630 | if '--' in stripped_line: | ||
| 631 | [fromDay,toDay] = stripped_line.split('--') | ||
| 632 | fromDay = parse_single(fromDay) | ||
| 633 | toDay = parse_single(toDay) | ||
| 634 | else: | ||
| 635 | fromDay = toDay = parse_single(stripped_line) | ||
| 636 | |||
| 637 | for day in [fromDay + timedelta(days = x) for x in range(0, (toDay - fromDay).days + 1)]: | ||
| 638 | extra_classified[day] = val | ||
| 639 | except IOError as e: | ||
| 640 | if e.errno != 2: | ||
| 641 | raise e | ||
| 642 | |||
| 643 | for day in days: | ||
| 644 | if not worktime.is_workday(day, extra=False): | ||
| 645 | continue | ||
| 646 | |||
| 647 | classification_days = set() | ||
| 648 | for default_classification in reversed(config.get("day-classification", {}).get(classification_name, {}).get("default", [])): | ||
| 649 | from_date = None | ||
| 650 | to_date = None | ||
| 651 | |||
| 652 | if "from" in default_classification: | ||
| 653 | from_date = datetime.strptime(default_classification["from"], date_format).replace(tzinfo=tzlocal()).date() | ||
| 654 | if day < from_date: | ||
| 655 | continue | ||
| 656 | |||
| 657 | if "to" in default_classification: | ||
| 658 | to_date = datetime.strptime(default_classification["to"], date_format).replace(tzinfo=tzlocal()).date() | ||
| 659 | if day >= to_date: | ||
| 660 | continue | ||
| 661 | |||
| 662 | classification_days = set([int(d.strip()) for d in default_classification.get("days", "").split(',') if d.strip()]) | ||
| 663 | |||
| 664 | default_classification = day.isoweekday() in classification_days | ||
| 665 | override = None | ||
| 666 | if day in extra_classified: | ||
| 667 | override = extra_classified[day] | ||
| 668 | if override != default_classification: | ||
| 669 | year_offset[day.year] += 1 if override else -1 | ||
| 670 | year_classification[day.year][day] = override if override is not None else default_classification | ||
| 671 | |||
| 672 | if not table: | ||
| 673 | print(sum(year_offset.values())) | ||
| 674 | else: | ||
| 675 | table_data = [] | ||
| 676 | for year in sorted(year_classification.keys()): | ||
| 677 | row_data = [year] | ||
| 678 | count_classified = len([1 for day, classified in year_classification[year].items() if classified]) | ||
| 679 | count_would_be_workdays = len([1 for day in days if day.year == year and worktime.would_be_workday(day) and day not in worktime.leave_days]) | ||
| 680 | row_data.append(year_offset[year]) | ||
| 681 | if len(year_classification[year]) != count_would_be_workdays: | ||
| 682 | row_data.append(f"{count_classified}/{len(year_classification[year])}/{count_would_be_workdays}") | ||
| 683 | else: | ||
| 684 | row_data.append(f"{count_classified}/{len(year_classification[year])}") | ||
| 685 | row_data.append(','.join(sorted([day.strftime('%m-%d') for day, classified in year_classification[year].items() if classified]))) | ||
| 686 | table_data.append(row_data) | ||
| 687 | print(tabulate(table_data, tablefmt="plain")) | ||
| 688 | |||
| 597 | def main(): | 689 | def main(): |
| 690 | config = Worktime.config() | ||
| 691 | |||
| 598 | parser = argparse.ArgumentParser(prog = "worktime", description = 'Track worktime using toggl API') | 692 | parser = argparse.ArgumentParser(prog = "worktime", description = 'Track worktime using toggl API') |
| 599 | parser.add_argument('--time', dest = 'now', metavar = 'TIME', type = lambda s: datetime.fromisoformat(s).replace(tzinfo=tzlocal()), help = 'Time to calculate status for (default: current time)', default = datetime.now(tzlocal())) | 693 | parser.add_argument('--time', dest = 'now', metavar = 'TIME', type = lambda s: datetime.fromisoformat(s).replace(tzinfo=tzlocal()), help = 'Time to calculate status for (default: current time)', default = datetime.now(tzlocal())) |
| 600 | parser.add_argument('--no-running', dest = 'include_running', action = 'store_false') | 694 | parser.add_argument('--no-running', dest = 'include_running', action = 'store_false') |
| @@ -613,6 +707,10 @@ def main(): | |||
| 613 | leave_parser.add_argument('year', metavar = 'YEAR', type = int, help = 'Year to evaluate leave days for (default: current year)', default = None, nargs='?') | 707 | leave_parser.add_argument('year', metavar = 'YEAR', type = int, help = 'Year to evaluate leave days for (default: current year)', default = None, nargs='?') |
| 614 | leave_parser.add_argument('--table', action = 'store_true') | 708 | leave_parser.add_argument('--table', action = 'store_true') |
| 615 | leave_parser.set_defaults(cmd = leave) | 709 | leave_parser.set_defaults(cmd = leave) |
| 710 | for classification_name in config.get('day-classification', {}).keys(): | ||
| 711 | classification_parser = subparsers.add_parser(classification_name) | ||
| 712 | classification_parser.add_argument('--table', action = 'store_true') | ||
| 713 | classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name)) | ||
| 616 | args = parser.parse_args() | 714 | args = parser.parse_args() |
| 617 | 715 | ||
| 618 | args.cmd(**vars(args)) | 716 | args.cmd(**vars(args)) |
