WIP
This commit is contained in:
218
src/calendar/backend.py
Normal file
218
src/calendar/backend.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""Calendar backend abstraction for Calendar TUI.
|
||||
|
||||
This module defines the abstract interface that all calendar backends must implement,
|
||||
allowing the TUI to work with different calendar systems (khal, calcurse, etc.)
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, date, time, timedelta
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
"""Unified calendar event representation across backends."""
|
||||
|
||||
uid: str
|
||||
title: str
|
||||
start: datetime
|
||||
end: datetime
|
||||
location: str = ""
|
||||
description: str = ""
|
||||
calendar: str = ""
|
||||
all_day: bool = False
|
||||
recurring: bool = False
|
||||
organizer: str = ""
|
||||
url: str = ""
|
||||
categories: str = ""
|
||||
status: str = "" # CONFIRMED, TENTATIVE, CANCELLED
|
||||
|
||||
@property
|
||||
def duration_minutes(self) -> int:
|
||||
"""Get duration in minutes."""
|
||||
delta = self.end - self.start
|
||||
return int(delta.total_seconds() / 60)
|
||||
|
||||
@property
|
||||
def start_time(self) -> time:
|
||||
"""Get start time."""
|
||||
return self.start.time()
|
||||
|
||||
@property
|
||||
def end_time(self) -> time:
|
||||
"""Get end time."""
|
||||
return self.end.time()
|
||||
|
||||
@property
|
||||
def date(self) -> date:
|
||||
"""Get the date of the event."""
|
||||
return self.start.date()
|
||||
|
||||
def overlaps(self, other: "Event") -> bool:
|
||||
"""Check if this event overlaps with another."""
|
||||
return self.start < other.end and self.end > other.start
|
||||
|
||||
def get_row_span(self, minutes_per_row: int = 30) -> Tuple[int, int]:
|
||||
"""Get the row range for this event in a grid.
|
||||
|
||||
Args:
|
||||
minutes_per_row: Minutes each row represents (default 30)
|
||||
|
||||
Returns:
|
||||
Tuple of (start_row, end_row) where rows are 0-indexed from midnight
|
||||
"""
|
||||
start_minutes = self.start.hour * 60 + self.start.minute
|
||||
end_minutes = self.end.hour * 60 + self.end.minute
|
||||
|
||||
# Handle events ending at midnight (next day)
|
||||
if end_minutes == 0 and self.end.date() > self.start.date():
|
||||
end_minutes = 24 * 60
|
||||
|
||||
start_row = start_minutes // minutes_per_row
|
||||
end_row = (end_minutes + minutes_per_row - 1) // minutes_per_row # Round up
|
||||
|
||||
return start_row, end_row
|
||||
|
||||
|
||||
class CalendarBackend(ABC):
|
||||
"""Abstract base class for calendar backends."""
|
||||
|
||||
@abstractmethod
|
||||
def get_events(
|
||||
self,
|
||||
start_date: date,
|
||||
end_date: date,
|
||||
calendar: Optional[str] = None,
|
||||
) -> List[Event]:
|
||||
"""Get events in a date range.
|
||||
|
||||
Args:
|
||||
start_date: Start of range (inclusive)
|
||||
end_date: End of range (inclusive)
|
||||
calendar: Optional calendar name to filter by
|
||||
|
||||
Returns:
|
||||
List of events in the range, sorted by start time
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_event(self, uid: str) -> Optional[Event]:
|
||||
"""Get a single event by UID.
|
||||
|
||||
Args:
|
||||
uid: Event unique identifier
|
||||
|
||||
Returns:
|
||||
Event if found, None otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_calendars(self) -> List[str]:
|
||||
"""Get list of available calendar names.
|
||||
|
||||
Returns:
|
||||
List of calendar names
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_event(
|
||||
self,
|
||||
title: str,
|
||||
start: datetime,
|
||||
end: datetime,
|
||||
calendar: Optional[str] = None,
|
||||
location: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
all_day: bool = False,
|
||||
) -> Event:
|
||||
"""Create a new event.
|
||||
|
||||
Args:
|
||||
title: Event title
|
||||
start: Start datetime
|
||||
end: End datetime
|
||||
calendar: Calendar to add event to
|
||||
location: Event location
|
||||
description: Event description
|
||||
all_day: Whether this is an all-day event
|
||||
|
||||
Returns:
|
||||
The created event
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_event(self, uid: str) -> bool:
|
||||
"""Delete an event.
|
||||
|
||||
Args:
|
||||
uid: Event unique identifier
|
||||
|
||||
Returns:
|
||||
True if deleted successfully
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_event(
|
||||
self,
|
||||
uid: str,
|
||||
title: Optional[str] = None,
|
||||
start: Optional[datetime] = None,
|
||||
end: Optional[datetime] = None,
|
||||
location: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
) -> Optional[Event]:
|
||||
"""Update an existing event.
|
||||
|
||||
Args:
|
||||
uid: Event unique identifier
|
||||
title: New title (if provided)
|
||||
start: New start time (if provided)
|
||||
end: New end time (if provided)
|
||||
location: New location (if provided)
|
||||
description: New description (if provided)
|
||||
|
||||
Returns:
|
||||
Updated event if successful, None otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_week_events(
|
||||
self,
|
||||
week_start: date,
|
||||
include_weekends: bool = True,
|
||||
) -> dict[date, List[Event]]:
|
||||
"""Get events for a week, grouped by date.
|
||||
|
||||
Args:
|
||||
week_start: First day of the week
|
||||
include_weekends: Whether to include Saturday/Sunday
|
||||
|
||||
Returns:
|
||||
Dict mapping dates to lists of events
|
||||
"""
|
||||
days = 7 if include_weekends else 5
|
||||
end_date = week_start + timedelta(days=days - 1)
|
||||
events = self.get_events(week_start, end_date)
|
||||
|
||||
# Group by date
|
||||
by_date: dict[date, List[Event]] = {}
|
||||
for i in range(days):
|
||||
d = week_start + timedelta(days=i)
|
||||
by_date[d] = []
|
||||
|
||||
for event in events:
|
||||
event_date = event.date
|
||||
if event_date in by_date:
|
||||
by_date[event_date].append(event)
|
||||
|
||||
# Sort each day's events by start time
|
||||
for d in by_date:
|
||||
by_date[d].sort(key=lambda e: e.start)
|
||||
|
||||
return by_date
|
||||
Reference in New Issue
Block a user