add new tasks

This commit is contained in:
Bendt
2025-12-18 15:40:03 -05:00
parent a63aadffcb
commit 0ed7800575
16 changed files with 1095 additions and 39 deletions

View File

@@ -1,3 +1,35 @@
"""
Mail utilities module for email operations.
"""
from .helpers import (
ensure_directory_exists,
format_datetime,
format_mail_link,
format_mail_link_comment,
format_mime_date,
has_mail_link,
load_last_sync_timestamp,
parse_mail_link,
parse_maildir_name,
remove_mail_link_comment,
safe_filename,
save_sync_timestamp,
truncate_id,
)
__all__ = [
"ensure_directory_exists",
"format_datetime",
"format_mail_link",
"format_mail_link_comment",
"format_mime_date",
"has_mail_link",
"load_last_sync_timestamp",
"parse_mail_link",
"parse_maildir_name",
"remove_mail_link_comment",
"safe_filename",
"save_sync_timestamp",
"truncate_id",
]

View File

@@ -1,12 +1,14 @@
"""
Mail utility helper functions.
"""
import os
import json
import time
from datetime import datetime
import email.utils
def truncate_id(message_id, length=8):
"""
Truncate a message ID to a reasonable length for display.
@@ -24,6 +26,7 @@ def truncate_id(message_id, length=8):
return message_id
return f"{message_id[:length]}..."
def load_last_sync_timestamp():
"""
Load the last synchronization timestamp from a file.
@@ -32,12 +35,13 @@ def load_last_sync_timestamp():
float: The timestamp of the last synchronization, or 0 if not available.
"""
try:
with open('sync_timestamp.json', 'r') as f:
with open("sync_timestamp.json", "r") as f:
data = json.load(f)
return data.get('timestamp', 0)
return data.get("timestamp", 0)
except (FileNotFoundError, json.JSONDecodeError):
return 0
def save_sync_timestamp():
"""
Save the current timestamp as the last synchronization timestamp.
@@ -46,8 +50,9 @@ def save_sync_timestamp():
None
"""
current_time = time.time()
with open('sync_timestamp.json', 'w') as f:
json.dump({'timestamp': current_time}, f)
with open("sync_timestamp.json", "w") as f:
json.dump({"timestamp": current_time}, f)
def format_datetime(dt_str, format_string="%m/%d %I:%M %p"):
"""
@@ -63,11 +68,12 @@ def format_datetime(dt_str, format_string="%m/%d %I:%M %p"):
if not dt_str:
return ""
try:
dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
return dt.strftime(format_string)
except (ValueError, AttributeError):
return dt_str
def format_mime_date(dt_str):
"""
Format a datetime string from ISO format to RFC 5322 format for MIME Date headers.
@@ -81,11 +87,12 @@ def format_mime_date(dt_str):
if not dt_str:
return ""
try:
dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
return email.utils.format_datetime(dt)
except (ValueError, AttributeError):
return dt_str
def safe_filename(filename):
"""
Convert a string to a safe filename.
@@ -98,9 +105,10 @@ def safe_filename(filename):
"""
invalid_chars = '<>:"/\\|?*'
for char in invalid_chars:
filename = filename.replace(char, '_')
filename = filename.replace(char, "_")
return filename
def ensure_directory_exists(directory):
"""
Ensure that a directory exists, creating it if necessary.
@@ -114,6 +122,7 @@ def ensure_directory_exists(directory):
if not os.path.exists(directory):
os.makedirs(directory)
def parse_maildir_name(filename):
"""
Parse a Maildir filename to extract components.
@@ -125,9 +134,104 @@ def parse_maildir_name(filename):
tuple: (message_id, flags) components of the filename.
"""
# Maildir filename format: unique-id:flags
if ':' in filename:
message_id, flags = filename.split(':', 1)
if ":" in filename:
message_id, flags = filename.split(":", 1)
else:
message_id = filename
flags = ''
flags = ""
return message_id, flags
# Mail-Task Link Utilities
# These functions handle the mail://message-id links that connect tasks to emails
MAIL_LINK_PREFIX = "mail://"
MAIL_LINK_COMMENT_PATTERN = r"<!--\s*mail://([^>\s]+)\s*-->"
def format_mail_link(message_id: str) -> str:
"""
Format a message ID as a mail link URI.
Args:
message_id: The email message ID (e.g., "abc123@example.com")
Returns:
Mail link URI (e.g., "mail://abc123@example.com")
"""
# Clean up message ID - remove angle brackets if present
message_id = message_id.strip()
if message_id.startswith("<"):
message_id = message_id[1:]
if message_id.endswith(">"):
message_id = message_id[:-1]
return f"{MAIL_LINK_PREFIX}{message_id}"
def format_mail_link_comment(message_id: str) -> str:
"""
Format a message ID as an HTML comment for embedding in task notes.
Args:
message_id: The email message ID
Returns:
HTML comment containing the mail link (e.g., "<!-- mail://abc123@example.com -->")
"""
return f"<!-- {format_mail_link(message_id)} -->"
def parse_mail_link(notes: str) -> str | None:
"""
Extract a mail link message ID from task notes.
Looks for an HTML comment in the format: <!-- mail://message-id -->
Args:
notes: The task notes content
Returns:
The message ID if found, None otherwise
"""
import re
if not notes:
return None
match = re.search(MAIL_LINK_COMMENT_PATTERN, notes)
if match:
return match.group(1)
return None
def has_mail_link(notes: str) -> bool:
"""
Check if task notes contain a mail link.
Args:
notes: The task notes content
Returns:
True if a mail link is found, False otherwise
"""
return parse_mail_link(notes) is not None
def remove_mail_link_comment(notes: str) -> str:
"""
Remove the mail link comment from task notes.
Args:
notes: The task notes content
Returns:
Notes with the mail link comment removed
"""
import re
if not notes:
return ""
# Remove the mail link comment and any trailing newlines
cleaned = re.sub(MAIL_LINK_COMMENT_PATTERN + r"\n*", "", notes)
return cleaned.strip()