ungana

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | README

commit 7c17d01b7abe1d5b73cc09e6cd5803772b803306
parent fa128da25e735a52d157e3c2f88ffeae83921689
Author: Carlosokumu <carlosokumu254@gmail.com>
Date:   Thu, 28 Aug 2025 15:58:24 +0300

update ArgsParser:
-  separate edit and create  required arguments
- ensure a safe timestamp  based filename is generated if not given

Diffstat:
Mungana/cmd/args_parser.py | 211++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
1 file changed, 129 insertions(+), 82 deletions(-)

diff --git a/ungana/cmd/args_parser.py b/ungana/cmd/args_parser.py @@ -18,7 +18,7 @@ class ArgsParser: ) self.ical_manager = ICalManager() self.attachment_manager = AttachmentManager() - self._add_field_arguments() + self._add_command_arguments() def _read_file_or_exit(self, file_path: str) -> str: try: @@ -51,54 +51,73 @@ class ArgsParser: ) return duration_str - def _add_field_arguments(self): + def _add_command_arguments(self): subparsers = self.parser.add_subparsers(dest='command', required=True) - - def add_event_field_args(parser): - parser.add_argument("-i", "--interactive", - action="store_true", - help="Run interactive calendar creation") - parser.add_argument("-s", "--summary", - help="Event summary (text or via --summary-file)") - parser.add_argument("--sf", "--summary-file", dest="summary_file", - help="File containing event summary") - parser.add_argument("-d", "--description", - help="Event description (text or via --description-file)") - parser.add_argument("--df", "--description-file", dest="description_file", - help="File containing event description") - parser.add_argument("--start", type=self._validate_datetime, - help="Event start time (ISO format or DD-MM-YYYY HH:MM)") - parser.add_argument("-l", "--location", - help="Event location") - parser.add_argument("-o", "--organizer", - help="Event organizer") - parser.add_argument("--tzid", help="Time zone ID (e.g., 'Europe/Berlin')") - parser.add_argument("--duration", type=self._validate_duration, - help="Event duration (e.g., '2h' or '30m')") - parser.add_argument("--end", type=self._validate_datetime, - help="Event end time (ISO format or DD-MM-YYYY HH:MM)") - parser.add_argument("-f", "--file", help="Output .ics filename (default: event_<date>.ics)") - parser.add_argument("-p", "--poster", help="Event headline image") - parser.add_argument("-ld", "--long", help="Exhaustive description of the event") - parser.add_argument("-c", "--contact", help="Contact details") - create_event_parser = subparsers.add_parser('create', help='Create a new calendar event') + self.add_create_args(create_event_parser) self._add_logging_arguments(create_event_parser) - create_event_parser.add_argument("ics_filename",help="Output .ics filename (default: event_<date>.ics") - add_event_field_args(create_event_parser) edit_event_parser = subparsers.add_parser('edit', help='Edit a calendar ical file') + self.add_edit_args(edit_event_parser) self._add_logging_arguments(edit_event_parser) edit_event_parser.add_argument("ics_file", help="Path to calendar .ics file") edit_event_parser.add_argument("-a", "--all", action="store_true", help="If your ical file has more than one event, interactively choose one to edit") - add_event_field_args(edit_event_parser) + + + def add_common_args(self, parser, required=False): + parser.add_argument("-i", "--interactive", action="store_true", + help="Run interactive calendar creation") + + parser.add_argument("-s", "--summary", required=required, help="Event summary") + parser.add_argument("--start", type=self._validate_datetime, required=required,help="Event start time (ISO format or DD-MM-YYYY HH:MM)") + parser.add_argument("-d", "--description",required=required, help="Event description") + parser.add_argument("-l", "--location",required=required,help="Event location") + parser.add_argument("-o", "--organizer",required=required,help="Event organizer") + + parser.add_argument("--sf", "--summary-file", dest="summary_file", help="File containing event summary") + parser.add_argument("--df", "--description-file", dest="description_file", help="File containing event description") + parser.add_argument("--tzid", help="Time zone ID") + parser.add_argument("--duration", type=self._validate_duration, help="Event duration") + parser.add_argument("--end", type=self._validate_datetime, help="Event end time") + + + def add_create_args(self, parser): + mode_group = parser.add_mutually_exclusive_group(required=False) + mode_group.add_argument( + "-i", "--interactive", + action="store_true", + help="Run interactive calendar creation" + ) + + non_interactive = parser.add_argument_group("non-interactive arguments") + non_interactive.add_argument("-s", "--summary", help="Event summary") + non_interactive.add_argument("--start", type=self._validate_datetime, help="Event start time (ISO format or DD-MM-YYYY HH:MM)") + non_interactive.add_argument("-d", "--description", help="Event description") + non_interactive.add_argument("-l", "--location", help="Event location") + non_interactive.add_argument("-o", "--organizer", help="Event organizer") + + non_interactive.add_argument("--sf", "--summary-file", dest="summary_file", help="File containing event summary") + non_interactive.add_argument("--df", "--description-file", dest="description_file", help="File containing event description") + non_interactive.add_argument("--tzid", help="Time zone ID") + non_interactive.add_argument("--duration", type=self._validate_duration, help="Event duration") + non_interactive.add_argument("--end", type=self._validate_datetime, help="Event end time") + parser.add_argument("ics_filename", nargs="?", help="Output .ics filename (default: event_<date>.ics)") + + + + def add_edit_args(self, parser): + self.add_common_args(parser, required=False) + parser.add_argument("-p", "--poster", help="Event headline image") + parser.add_argument("-ld", "--long", help="Exhaustive description of the event") + parser.add_argument("-c", "--contact", help="Contact details") + + def _add_logging_arguments(self, parser): - parser.add_argument("-v", "--verbose", action="store_true", - help="Enable verbose debug output") + parser.add_argument("-v", "--verbose", action="store_true",help="Enable verbose debug output") parser.add_argument("-q", "--quiet", action="store_true", help="Suppress all non-error output") @@ -153,64 +172,42 @@ class ArgsParser: def handle_create(self, args): if args.interactive: if args.ics_filename: - cal = self.ical_manager.load_ical_file(args.ics_filename) - if cal: + ics_filename = args.ics_filename + else: + ics_filename = f"event_{datetime.now().strftime('%Y%m%d_%H%M%S')}.ics" + + cal = self.ical_manager.load_ical_file(ics_filename) + if cal: event_data = self.prompt_compulsory_event_fields() event = self.ical_manager.create_event(event_data) - self.ical_manager.save_ical_file(event, args.ics_filename) + self.ical_manager.save_ical_file(event, ics_filename) + return else: - if args.summary_file: - args.summary = self._read_file_or_exit(args.summary_file) - if args.description_file: - args.description = self._read_file_or_exit(args.description_file) - - if not args.end and not args.duration: - self.parser.error("Either --end or --duration must be specified") + event_data = self._validate_and_get_event_args(args) - args.start_dt = datetime.fromisoformat(args.start) - - if args.end: - end_dt = datetime.fromisoformat(args.end) - duration = end_dt - args.start_dt - args.duration = f"{duration.seconds//3600}h{(duration.seconds%3600)//60}m" - - if args.tzid: - tzinfo = tz.gettz(args.tzid) - if tzinfo is None: - self.parser.error(f"Invalid timezone ID: {args.tzid}") - else: - # Fallback to UTC - tzinfo = tz.UTC - args.tzid = "UTC" - - event_data = { - 'start': args.start_dt, - 'duration': args.duration, - 'summary': args.summary, - 'location': args.location, - 'description': args.description, - 'organizer': args.organizer, - 'tzid': args.tzid - } if args.ics_filename: filename = args.ics_filename cal = self.ical_manager.load_ical_file(filename) - exists = self.ical_manager.check_existing_event(cal,event_data) + exists = self.ical_manager.check_existing_event(cal, event_data) if exists: details = ( - f"Summary='{event_data.get('summary')}', " - f"Start={event_data.get('start')}, " - f"Tzid={event_data.get('tzid')}, " - f"Location='{event_data.get('location')}'" - ) - self.parser.error( f"Duplicate event detected: An event with these details already exists: {details}. "f"Try using the 'edit' command instead.") - return + f"Summary='{event_data['summary']}', " + f"Start={event_data['start']}, " + f"Tzid={event_data['tzid']}, " + f"Location='{event_data['location']}'" + ) + self.parser.error( + f"Duplicate event detected: An event with these details already exists: {details}. " + f"Try using the 'edit' command instead." + ) else: filename = f"event_{datetime.now().strftime('%Y%m%d_%H%M%S')}.ics" - + event = self.ical_manager.create_event(event_data) self.ical_manager.save_ical_file(event, filename) + + def handle_edit(self, args): if not args.ics_file: @@ -283,8 +280,6 @@ class ArgsParser: logging.info("No changes made to calendar file") - - def _get_user_event_updates(self, event) -> dict: updates = {} editable_fields = [ @@ -372,6 +367,58 @@ class ArgsParser: return raw_contact.strip(), {} + def _validate_and_get_event_args(self, args): + if args.summary_file: + args.summary = self._read_file_or_exit(args.summary_file) + if args.description_file: + args.description = self._read_file_or_exit(args.description_file) + + if not args.summary: + self.parser.error("Event summary must be specified (use -s or --sf).") + if not args.description: + self.parser.error("Event description must be specified (use -d or --df).") + if not args.location: + self.parser.error("Event location must be specified (-l).") + if not args.organizer: + self.parser.error("Event organizer must be specified (-o).") + + if not args.start: + self.parser.error("Start time must be specified (--start).") + try: + args.start_dt = datetime.fromisoformat(args.start) + except ValueError: + self.parser.error(f"Invalid start time format: {args.start}") + + if not args.end and not args.duration: + self.parser.error("Either --end or --duration must be specified.") + + if args.end: + try: + end_dt = datetime.fromisoformat(args.end) + except ValueError: + self.parser.error(f"Invalid end time format: {args.end}") + duration = end_dt - args.start_dt + args.duration = f"{duration.seconds//3600}h{(duration.seconds%3600)//60}m" + + if args.tzid: + tzinfo = tz.gettz(args.tzid) + if tzinfo is None: + self.parser.error(f"Invalid timezone ID: {args.tzid}") + else: + tzinfo = tz.UTC + args.tzid = "UTC" + + event_data = { + 'summary': args.summary, + 'description': args.description, + 'location': args.location, + 'organizer': args.organizer, + 'start': args.start_dt, + 'duration': args.duration, + 'tzid': args.tzid, + } + + return event_data def run(self):