From dc587f322aa3acae72579968050c6a3e09532b63 Mon Sep 17 00:00:00 2001 From: nicfb Date: Sun, 2 Oct 2022 00:50:02 -0500 Subject: [PATCH] feat: add event searching only parse from and to dates when searching toggle between date formats when searching refactoring change date formatting for searched events raise exception when search dates not given --- kosmorro/__main__.py | 116 +++++++++++++++++++++++++++++++++++++---- kosmorro/dumper.py | 7 ++- kosmorro/exceptions.py | 21 ++++++++ 3 files changed, 132 insertions(+), 12 deletions(-) diff --git a/kosmorro/__main__.py b/kosmorro/__main__.py index 5660cb42..18465a9f 100644 --- a/kosmorro/__main__.py +++ b/kosmorro/__main__.py @@ -21,8 +21,15 @@ import os.path from babel.dates import format_date -from kosmorrolib import Position, get_ephemerides, get_events, get_moon_phase -from kosmorrolib.exceptions import OutOfRangeDateError +from kosmorrolib import ( + Position, + get_ephemerides, + get_events, + get_moon_phase, + search_events, +) +from kosmorrolib.exceptions import OutOfRangeDateError, InvalidDateRangeError +from kosmorrolib.enum import EventType from datetime import date from . import dumper, environment, debug @@ -37,6 +44,7 @@ ) from .exceptions import ( InvalidOutputFormatError, + SearchDatesNotGivenError, UnavailableFeatureError, OutOfRangeDateError as DateRangeError, ) @@ -55,6 +63,8 @@ def run(): if args.special_action is not None: return 0 if args.special_action() else 1 + search_enabled = args.search is not None + try: compute_date = parse_date(args.date) except ValueError as error: @@ -105,14 +115,34 @@ def run(): try: use_colors = not environment.NO_COLOR and args.colors - output = get_information( - compute_date, - position, - timezone, - output_format, - use_colors, - args.show_graph, - ) + output = None + if search_enabled: + output = get_search_information( + args.search, + args.from_, + args.to, + timezone, + output_format, + use_colors, + args.show_graph, + ) + else: + output = get_information( + compute_date, + position, + timezone, + output_format, + use_colors, + args.show_graph, + ) + except SearchDatesNotGivenError as error: + print_stderr(colored(error.msg, "red")) + debug.debug_print(error) + return 5 + except ValueError as error: + print_stderr(colored(error.args[0], color="red", attrs=["bold"])) + debug.debug_print(error) + return 4 except InvalidOutputFormatError as error: print_stderr(colored(error.msg, "red")) debug.debug_print(error) @@ -204,6 +234,7 @@ def get_information( timezone=timezone, with_colors=colors, show_graph=show_graph, + search_enabled=False, ) except KeyError as error: raise InvalidOutputFormatError(output_format, list(get_dumpers().keys())) @@ -211,6 +242,53 @@ def get_information( raise DateRangeError(error.min_date, error.max_date) +def get_search_information( + requested_events: [EventType], + search_from: date, + search_to: date, + timezone: int, + output_format: str, + colors: bool, + show_graph: bool, +) -> dumper.Dumper: + try: + if search_from is None or search_to is None: + raise SearchDatesNotGivenError + + event_types = [EventType[event.upper()] for event in requested_events] + from_ = parse_date(search_from) + to = parse_date(search_to) + events_list = search_events(event_types, to, from_, timezone) + + return get_dumpers()[output_format]( + ephemerides=[], + moon_phase=None, + events=events_list, + date=None, + timezone=timezone, + with_colors=colors, + show_graph=show_graph, + search_enabled=True, + ) + except InvalidDateRangeError as error: + print( + colored( + _( + "Search start date {start_search} must be before end date {end_search}" + ).format( + start_search=format_date(error.start_date, "long"), + end_search=format_date(error.end_date, "long"), + ), + "red", + ) + ) + except KeyError as error: + raise InvalidOutputFormatError(output_format, list(get_dumpers().keys())) + except OutOfRangeDateError as error: + print(colored(error.msg, "red")) + debug.debug_print(error) + + def get_dumpers() -> {str: dumper.Dumper}: return { "txt": dumper.TextDumper, @@ -308,6 +386,24 @@ def get_args(output_formats: [str]): "Can also be set in the KOSMORRO_TIMEZONE environment variable." ), ) + parser.add_argument( + "--search", + "-s", + type=str, + nargs="*", + default=None, + help=_("Search for specific event types by date."), + ) + parser.add_argument( + "--from", + dest="from_", + type=str, + default=None, + help=_("The date to begin searching for events."), + ) + parser.add_argument( + "--to", type=str, default=None, help=_("The date to end searching for events.") + ) parser.add_argument( "--no-colors", dest="colors", diff --git a/kosmorro/dumper.py b/kosmorro/dumper.py index 5d5c7e39..6a697dc2 100644 --- a/kosmorro/dumper.py +++ b/kosmorro/dumper.py @@ -25,7 +25,7 @@ import shutil from pathlib import Path -from babel.dates import format_date, format_time +from babel.dates import format_date, format_time, format_datetime from tabulate import tabulate from termcolor import colored @@ -60,6 +60,7 @@ def __init__( timezone: int, with_colors: bool, show_graph: bool, + search_enabled: bool, ): self.ephemerides = ephemerides self.moon_phase = moon_phase @@ -68,6 +69,7 @@ def __init__( self.timezone = timezone self.with_colors = with_colors self.show_graph = show_graph + self.search_enabled = search_enabled def get_date_as_string(self, capitalized: bool = False) -> str: date = format_date(self.date, "full") @@ -220,9 +222,10 @@ def get_events(self, events: [Event]) -> str: if description is None: continue + date_format = "MMM dd, yyyy hh:mm a" if self.search_enabled else "hh:mm a" data.append( [ - self.style(format_time(event.start_time, "short"), "th"), + self.style(format_datetime(event.start_time, date_format), "th"), description, ] ) diff --git a/kosmorro/exceptions.py b/kosmorro/exceptions.py index c5e5ca37..68e42149 100644 --- a/kosmorro/exceptions.py +++ b/kosmorro/exceptions.py @@ -40,6 +40,19 @@ def __init__(self, min_date: date, max_date: date): ) +class InvalidDateRangeError(RuntimeError): + def __init__(self, start_date: date, end_date: date): + super().__init__() + self.start_date = start_date + self.end_date = end_date + self.msg = _( + "The start date {starting_date} must be before the end date {ending_date}" + ).format( + starting_date=format_date(start_date, "long"), + ending_date=format_date(end_date, "long"), + ) + + class InvalidOutputFormatError(RuntimeError): def __init__(self, output_format: str, accepted_extensions: [str]): super().__init__() @@ -53,6 +66,14 @@ def __init__(self, output_format: str, accepted_extensions: [str]): ) +class SearchDatesNotGivenError(RuntimeError): + def __init__(self): + super().__init__() + self.msg = _( + "Both 'from' and 'to' dates are required when searching for events." + ) + + class CompileError(RuntimeError): def __init__(self, msg): super().__init__()