Source code for git_timesheet.formatters
#!/usr/bin/env python3
import os
from datetime import datetime, timedelta
from collections import defaultdict
from .timezone_utils import convert_to_timezone, get_timezone_abbr
[docs]
def format_timesheet(time_entries, output_format='text', timezone_str='UTC', author_filter='mcgarrah'):
"""Format time entries into a weekly timesheet."""
if not time_entries:
return "No git activity found in the specified time period."
# Filter for entries with author_filter in author name or email
if author_filter:
filtered_entries = [entry for entry in time_entries
if author_filter.lower() in entry['author_name'].lower() or
author_filter.lower() in entry['author_email'].lower()]
if not filtered_entries:
return f"No git activity found for the specified author in the given time period."
time_entries = filtered_entries
# Convert dates to specified timezone
for entry in time_entries:
entry['date'] = convert_to_timezone(entry['date'], timezone_str)
# Group by week and day
weeks = defaultdict(lambda: defaultdict(list))
for entry in time_entries:
date = entry['date']
week_start = (date - timedelta(days=date.weekday())).strftime('%Y-%m-%d')
day = date.strftime('%Y-%m-%d')
weeks[week_start][day].append(entry)
if output_format == 'text':
return format_text(weeks)
elif output_format == 'csv':
return format_csv(weeks, time_entries)
elif output_format in ['markdown', 'md']:
return format_markdown(weeks)
else:
return format_text(weeks) # Default to text
[docs]
def format_text(weeks):
"""Format timesheet as plain text."""
result = []
for week_start, days in sorted(weeks.items()):
result.append(f"\\nWeek of {week_start}")
result.append("=" * 80)
week_total = 0
for day, entries in sorted(days.items()):
day_date = datetime.strptime(day, '%Y-%m-%d')
day_name = day_date.strftime('%A')
day_total = sum(entry['minutes'] for entry in entries)
week_total += day_total
result.append(f"\\n{day_name}, {day} - Total: {day_total/60:.2f} hours")
result.append("-" * 80)
# Group by repository
repos = defaultdict(list)
for entry in entries:
repos[entry['repo']].append(entry)
for repo, repo_entries in sorted(repos.items()):
repo_name = os.path.basename(repo)
repo_total = sum(entry['minutes'] for entry in repo_entries)
result.append(f"\\n {repo_name} - {repo_total/60:.2f} hours")
for entry in repo_entries:
time_str = f"{entry['minutes']/60:.2f}h"
commit_time = entry['date'].strftime('%H:%M')
tz_abbr = get_timezone_abbr(entry['date'])
result.append(f" {commit_time} {tz_abbr} - {time_str} - {entry['message'][:60]} ({entry['commit'][:7]}) - {entry['author_name']}")
result.append(f"\\nWeek Total: {week_total/60:.2f} hours\\n")
result.append("=" * 80)
return "\\n".join(result).replace('\\n', '\n')
[docs]
def format_csv(weeks, time_entries):
"""Format timesheet as CSV."""
output = []
# Write to string buffer
output.append("Date,Day,Week,Start Time,Timezone,Duration (min),Duration (hours),Repository,Commit,Message,Author")
for entry in sorted(time_entries, key=lambda x: x['date']):
date = entry['date']
week_start = (date - timedelta(days=date.weekday())).strftime('%Y-%m-%d')
day_name = date.strftime('%A')
date_str = date.strftime('%Y-%m-%d')
time_str = date.strftime('%H:%M')
tz_abbr = get_timezone_abbr(date)
repo_name = os.path.basename(entry['repo'])
# Escape any commas in the message
message = entry['message'].replace('"', '""')
line = f'"{date_str}","{day_name}","{week_start}","{time_str}","{tz_abbr}",{entry["minutes"]},{entry["minutes"]/60:.2f},"{repo_name}","{entry["commit"][:7]}","{message}","{entry["author_name"]}"'
output.append(line)
return "\\n".join(output).replace('\\n', '\n')
[docs]
def format_markdown(weeks):
"""Format timesheet as Markdown."""
result = []
result.append("# Git Activity Timesheet\\n")
for week_start, days in sorted(weeks.items()):
result.append(f"## Week of {week_start}\\n")
# Create a table for the week
result.append("| Day | Date | Time | TZ | Repository | Hours | Description |")
result.append("|-----|------|------|-------|------------|-------|-------------|")
week_total = 0
# Sort days to ensure Monday-Sunday order
sorted_days = sorted(days.items())
for day, entries in sorted_days:
day_date = datetime.strptime(day, '%Y-%m-%d')
day_name = day_date.strftime('%A')
day_total = sum(entry['minutes'] for entry in entries)
week_total += day_total
# Group by repository
repos = defaultdict(list)
for entry in entries:
repos[entry['repo']].append(entry)
# First row for the day includes the day name
first_row = True
for repo, repo_entries in sorted(repos.items()):
repo_name = os.path.basename(repo)
repo_total = sum(entry['minutes'] for entry in repo_entries)
# Group entries by similar tasks
tasks = defaultdict(list)
for entry in repo_entries:
# Use first 30 chars of message as key
key = entry['message'][:30]
tasks[key].append(entry)
for task_name, task_entries in tasks.items():
task_total = sum(entry['minutes'] for entry in task_entries)
task_desc = f"{task_name}... ({len(task_entries)} commits)"
# Get the time of the first commit in this task group
first_commit = min(task_entries, key=lambda x: x['date'])
first_commit_time = first_commit['date'].strftime('%H:%M')
tz_abbr = get_timezone_abbr(first_commit['date'])
if first_row:
result.append(f"| {day_name} | {day} | {first_commit_time} | {tz_abbr} | {repo_name} | {task_total/60:.2f} | {task_desc} |")
first_row = False
else:
result.append(f"| | | {first_commit_time} | {tz_abbr} | {repo_name} | {task_total/60:.2f} | {task_desc} |")
# Add day total
result.append(f"| **Total** | | | | | **{day_total/60:.2f}** | |")
result.append("| | | | | | | |") # Empty row for readability
# Add week total
result.append(f"| **Week Total** | | | | | **{week_total/60:.2f}** | |")
result.append("\\n")
return "\\n".join(result).replace('\\n', '\n')