diff --git a/kosmorro/__main__.py b/kosmorro/__main__.py index 5660cb4..18465a9 100644 --- a/kosmorro/__main__.py +++ b/kosmorro/__main__.py @@ -21,8 +21,15 @@ import sys 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 .utils import ( ) 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 5d5c7e3..6a697dc 100644 --- a/kosmorro/dumper.py +++ b/kosmorro/dumper.py @@ -25,7 +25,7 @@ import subprocess 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 @@ class Dumper(ABC): timezone: int, with_colors: bool, show_graph: bool, + search_enabled: bool, ): self.ephemerides = ephemerides self.moon_phase = moon_phase @@ -68,6 +69,7 @@ class Dumper(ABC): 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 @@ class TextDumper(Dumper): 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 c5e5ca3..68e4214 100644 --- a/kosmorro/exceptions.py +++ b/kosmorro/exceptions.py @@ -40,6 +40,19 @@ class OutOfRangeDateError(RuntimeError): ) +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 @@ class InvalidOutputFormatError(RuntimeError): ) +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__()