commit e86d18c274dc4097ef28f9c3bb774af7b9a67dba
parent 7c17d01b7abe1d5b73cc09e6cd5803772b803306
Author: Carlosokumu <carlosokumu254@gmail.com>
Date: Fri, 29 Aug 2025 12:52:46 +0300
add user input validation
Diffstat:
1 file changed, 64 insertions(+), 35 deletions(-)
diff --git a/ungana/cmd/args_parser.py b/ungana/cmd/args_parser.py
@@ -3,6 +3,7 @@ from pathlib import Path
from datetime import datetime, timedelta
import re
import logging
+import sys
from ungana.attachment.attachment_manager import AttachmentManager
from ungana.ical.ical_manager import ICalManager
from ungana.logging.logging_manager import LoggingManager
@@ -128,44 +129,72 @@ class ArgsParser:
self.logger = self.logging.get_logger("ArgsParser")
return args
-
+
+ def _prompt_datetime(self, prompt: str, allow_blank: bool = False):
+ """Prompt user for a datetime in YYYY-MM-DD HH:MM format (with retry)."""
+ while True:
+ value = input(prompt).strip()
+ if allow_blank and not value:
+ return None
+ try:
+ return datetime.strptime(value, "%Y-%m-%d %H:%M")
+ except ValueError:
+ prog = self.parser.prog if hasattr(self, "parser") else "program"
+ print(f"{prog}: error: invalid datetime format, expected YYYY-MM-DD HH:MM")
+
+ def _prompt_timezone(self):
+ """Prompt for timezone ID with validation."""
+ while True:
+ try:
+ tzid = input("Enter timezone ID (e.g. Europe/Berlin) [default: UTC]: ").strip() or "UTC"
+ tzinfo = tz.gettz(tzid)
+ if tzinfo is None:
+ print(f"Invalid timezone ID: {tzid}. Please try again.")
+ else:
+ return tzid
+ except KeyboardInterrupt:
+ prog = self.parser.prog if hasattr(self, "parser") else "program"
+ print(f"\n{prog}: cancelled by user")
+ sys.exit(1)
+
def prompt_compulsory_event_fields(self):
- """Prompt user for compulsory event fields interactively."""
- summary = input("Enter event summary (title): ").strip()
- description = input("Enter description: ").strip()
- start = input("Enter start datetime (YYYY-MM-DD HH:MM): ").strip()
-
- end = input("Enter end datetime (YYYY-MM-DD HH:MM) [leave blank if using duration]: ").strip()
- start_dt = datetime.strptime(start, "%Y-%m-%d %H:%M")
+ try:
+ summary = input("Enter event summary (title): ").strip()
+ description = input("Enter description: ").strip()
+
+ start_dt = self._prompt_datetime("Enter start datetime (YYYY-MM-DD HH:MM): ")
+ end_dt = self._prompt_datetime(
+ "Enter end datetime (YYYY-MM-DD HH:MM) [leave blank if using duration]: ",
+ allow_blank=True
+ )
+
+ if end_dt:
+ duration_dt = end_dt - start_dt
+ duration = f"{duration_dt.seconds//3600}h{(duration_dt.seconds%3600)//60}m"
+ else:
+ duration = input("Enter duration (e.g. 1h, 30m): ").strip()
+
+ location = input("Enter location: ").strip()
+ organizer = input("Enter organizer (name/email): ").strip()
+ tzid = self._prompt_timezone()
+
+ return {
+ "summary": summary,
+ "description": description,
+ "start": start_dt,
+ "end": end_dt,
+ "duration": duration if duration else None,
+ "location": location,
+ "organizer": organizer,
+ "tzid": tzid,
+ }
+
+ except KeyboardInterrupt:
+ prog = self.parser.prog if hasattr(self, "parser") else "program"
+ print(f"\n{prog}: cancelled by user")
+ sys.exit(1)
- if not end:
- duration = input("Enter duration (e.g. 1h, 30m): ").strip()
- else:
- end_dt = datetime.strptime(end, "%Y-%m-%d %H:%M")
- delta = end_dt - start_dt
- hours, remainder = divmod(delta.seconds, 3600)
- minutes, _ = divmod(remainder, 60)
- duration = f"PT{delta.days}D{hours}H{minutes}M" if delta.days else f"PT{hours}H{minutes}M"
-
-
- location = input("Enter location: ").strip()
- organizer = input("Enter organizer (name/email): ").strip()
-
- tzid = input("Enter timezone ID (e.g. Europe/Berlin) [default: UTC]: ").strip()
- if not tzid:
- tzid = "UTC"
-
- return {
- "summary": summary,
- "description": description,
- "start": start,
- "end": end if end else None,
- "duration": duration if duration else None,
- "location": location,
- "organizer": organizer,
- "tzid": tzid,
- }