add task ui
This commit is contained in:
259
src/tasks/backend.py
Normal file
259
src/tasks/backend.py
Normal file
@@ -0,0 +1,259 @@
|
||||
"""Task backend abstraction for Tasks TUI.
|
||||
|
||||
This module defines the abstract interface that all task backends must implement,
|
||||
allowing the TUI to work with different task management systems (dstask, taskwarrior, etc.)
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class TaskStatus(Enum):
|
||||
"""Task status values."""
|
||||
|
||||
PENDING = "pending"
|
||||
ACTIVE = "active"
|
||||
DONE = "done"
|
||||
DELETED = "deleted"
|
||||
|
||||
|
||||
class TaskPriority(Enum):
|
||||
"""Task priority levels (P0 = highest, P3 = lowest)."""
|
||||
|
||||
P0 = "P0" # Critical
|
||||
P1 = "P1" # High
|
||||
P2 = "P2" # Normal
|
||||
P3 = "P3" # Low
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> "TaskPriority":
|
||||
"""Parse priority from string."""
|
||||
value = value.upper().strip()
|
||||
if value in ("P0", "0", "CRITICAL"):
|
||||
return cls.P0
|
||||
elif value in ("P1", "1", "HIGH", "H"):
|
||||
return cls.P1
|
||||
elif value in ("P2", "2", "NORMAL", "MEDIUM", "M"):
|
||||
return cls.P2
|
||||
elif value in ("P3", "3", "LOW", "L"):
|
||||
return cls.P3
|
||||
return cls.P2 # Default to normal
|
||||
|
||||
|
||||
@dataclass
|
||||
class Task:
|
||||
"""Unified task representation across backends."""
|
||||
|
||||
uuid: str
|
||||
id: int # Short numeric ID for display
|
||||
summary: str
|
||||
status: TaskStatus = TaskStatus.PENDING
|
||||
priority: TaskPriority = TaskPriority.P2
|
||||
project: str = ""
|
||||
tags: list[str] = field(default_factory=list)
|
||||
notes: str = ""
|
||||
due: Optional[datetime] = None
|
||||
created: Optional[datetime] = None
|
||||
resolved: Optional[datetime] = None
|
||||
|
||||
@property
|
||||
def is_overdue(self) -> bool:
|
||||
"""Check if task is overdue."""
|
||||
if self.due is None or self.status == TaskStatus.DONE:
|
||||
return False
|
||||
# Use timezone-aware now() if due date is timezone-aware
|
||||
now = datetime.now(timezone.utc) if self.due.tzinfo else datetime.now()
|
||||
return now > self.due
|
||||
|
||||
|
||||
@dataclass
|
||||
class Project:
|
||||
"""Project information."""
|
||||
|
||||
name: str
|
||||
task_count: int = 0
|
||||
resolved_count: int = 0
|
||||
active: bool = True
|
||||
priority: TaskPriority = TaskPriority.P2
|
||||
|
||||
|
||||
class TaskBackend(ABC):
|
||||
"""Abstract base class for task management backends."""
|
||||
|
||||
@abstractmethod
|
||||
def get_tasks(
|
||||
self,
|
||||
project: Optional[str] = None,
|
||||
tags: Optional[list[str]] = None,
|
||||
status: Optional[TaskStatus] = None,
|
||||
) -> list[Task]:
|
||||
"""Get tasks, optionally filtered by project, tags, or status.
|
||||
|
||||
Args:
|
||||
project: Filter by project name
|
||||
tags: Filter by tags (tasks must have all specified tags)
|
||||
status: Filter by status
|
||||
|
||||
Returns:
|
||||
List of matching tasks
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_next_tasks(self) -> list[Task]:
|
||||
"""Get the 'next' tasks to work on (priority-sorted actionable tasks)."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_task(self, task_id: str) -> Optional[Task]:
|
||||
"""Get a single task by ID or UUID.
|
||||
|
||||
Args:
|
||||
task_id: Task ID (numeric) or UUID
|
||||
|
||||
Returns:
|
||||
Task if found, None otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_task(
|
||||
self,
|
||||
summary: str,
|
||||
project: Optional[str] = None,
|
||||
tags: Optional[list[str]] = None,
|
||||
priority: Optional[TaskPriority] = None,
|
||||
due: Optional[datetime] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> Task:
|
||||
"""Create a new task.
|
||||
|
||||
Args:
|
||||
summary: Task description
|
||||
project: Project name
|
||||
tags: List of tags
|
||||
priority: Task priority
|
||||
due: Due date
|
||||
notes: Additional notes
|
||||
|
||||
Returns:
|
||||
The created task
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def complete_task(self, task_id: str) -> bool:
|
||||
"""Mark a task as complete.
|
||||
|
||||
Args:
|
||||
task_id: Task ID or UUID
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_task(self, task_id: str) -> bool:
|
||||
"""Delete a task.
|
||||
|
||||
Args:
|
||||
task_id: Task ID or UUID
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def start_task(self, task_id: str) -> bool:
|
||||
"""Start working on a task (mark as active).
|
||||
|
||||
Args:
|
||||
task_id: Task ID or UUID
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop_task(self, task_id: str) -> bool:
|
||||
"""Stop working on a task (mark as pending).
|
||||
|
||||
Args:
|
||||
task_id: Task ID or UUID
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def modify_task(
|
||||
self,
|
||||
task_id: str,
|
||||
summary: Optional[str] = None,
|
||||
project: Optional[str] = None,
|
||||
tags: Optional[list[str]] = None,
|
||||
priority: Optional[TaskPriority] = None,
|
||||
due: Optional[datetime] = None,
|
||||
notes: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Modify a task.
|
||||
|
||||
Args:
|
||||
task_id: Task ID or UUID
|
||||
summary: New summary (if provided)
|
||||
project: New project (if provided)
|
||||
tags: New tags (if provided)
|
||||
priority: New priority (if provided)
|
||||
due: New due date (if provided)
|
||||
notes: New notes (if provided)
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_projects(self) -> list[Project]:
|
||||
"""Get all projects.
|
||||
|
||||
Returns:
|
||||
List of projects with task counts
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_tags(self) -> list[str]:
|
||||
"""Get all tags.
|
||||
|
||||
Returns:
|
||||
List of tag names
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def sync(self) -> bool:
|
||||
"""Sync tasks with remote (if supported).
|
||||
|
||||
Returns:
|
||||
True if successful (or not applicable)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def edit_task_interactive(self, task_id: str) -> bool:
|
||||
"""Open task in editor for interactive editing.
|
||||
|
||||
Args:
|
||||
task_id: Task ID or UUID
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
Reference in New Issue
Block a user